Merge "Make ArrayMap public! :)"
diff --git a/api/current.txt b/api/current.txt
index e0025ea..9073348 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24794,6 +24794,33 @@
     ctor public AndroidRuntimeException(java.lang.Exception);
   }
 
+  public final class ArrayMap implements java.util.Map {
+    ctor public ArrayMap();
+    ctor public ArrayMap(int);
+    ctor public ArrayMap(android.util.ArrayMap);
+    method public void clear();
+    method public boolean containsAll(java.util.Collection<?>);
+    method public boolean containsKey(java.lang.Object);
+    method public boolean containsValue(java.lang.Object);
+    method public void ensureCapacity(int);
+    method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
+    method public V get(java.lang.Object);
+    method public boolean isEmpty();
+    method public K keyAt(int);
+    method public java.util.Set<K> keySet();
+    method public V put(K, V);
+    method public void putAll(android.util.ArrayMap<? extends K, ? extends V>);
+    method public void putAll(java.util.Map<? extends K, ? extends V>);
+    method public V remove(java.lang.Object);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public V removeAt(int);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public V setValueAt(int, V);
+    method public int size();
+    method public V valueAt(int);
+    method public java.util.Collection<V> values();
+  }
+
   public class AtomicFile {
     ctor public AtomicFile(java.io.File);
     method public void delete();
@@ -25147,6 +25174,7 @@
     method public void put(int, E);
     method public void remove(int);
     method public void removeAt(int);
+    method public void removeAtRange(int, int);
     method public void setValueAt(int, E);
     method public int size();
     method public E valueAt(int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index fa746ba..8f2f9ca 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.util.ArrayMap;
 import com.android.internal.app.ActionBarImpl;
 import com.android.internal.policy.PolicyManager;
 
@@ -700,7 +701,7 @@
         Object activity;
         HashMap<String, Object> children;
         ArrayList<Fragment> fragments;
-        HashMap<String, LoaderManagerImpl> loaders;
+        ArrayMap<String, LoaderManagerImpl> loaders;
     }
     /* package */ NonConfigurationInstances mLastNonConfigurationInstances;
     
@@ -725,7 +726,7 @@
         }
     };
     
-    HashMap<String, LoaderManagerImpl> mAllLoaderManagers;
+    ArrayMap<String, LoaderManagerImpl> mAllLoaderManagers;
     LoaderManagerImpl mLoaderManager;
     
     private static final class ManagedCursor {
@@ -819,13 +820,13 @@
             return mLoaderManager;
         }
         mCheckedForLoaderManager = true;
-        mLoaderManager = getLoaderManager(null, mLoadersStarted, true);
+        mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
         return mLoaderManager;
     }
     
     LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
         if (mAllLoaderManagers == null) {
-            mAllLoaderManagers = new HashMap<String, LoaderManagerImpl>();
+            mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>();
         }
         LoaderManagerImpl lm = mAllLoaderManagers.get(who);
         if (lm == null) {
@@ -1036,7 +1037,7 @@
             if (mLoaderManager != null) {
                 mLoaderManager.doStart();
             } else if (!mCheckedForLoaderManager) {
-                mLoaderManager = getLoaderManager(null, mLoadersStarted, false);
+                mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
             }
             mCheckedForLoaderManager = true;
         }
@@ -1648,17 +1649,18 @@
         if (mAllLoaderManagers != null) {
             // prune out any loader managers that were already stopped and so
             // have nothing useful to retain.
-            LoaderManagerImpl loaders[] = new LoaderManagerImpl[mAllLoaderManagers.size()];
-            mAllLoaderManagers.values().toArray(loaders);
-            if (loaders != null) {
-                for (int i=0; i<loaders.length; i++) {
-                    LoaderManagerImpl lm = loaders[i];
-                    if (lm.mRetaining) {
-                        retainLoaders = true;
-                    } else {
-                        lm.doDestroy();
-                        mAllLoaderManagers.remove(lm.mWho);
-                    }
+            final int N = mAllLoaderManagers.size();
+            LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
+            for (int i=N-1; i>=0; i--) {
+                loaders[i] = mAllLoaderManagers.valueAt(i);
+            }
+            for (int i=0; i<N; i++) {
+                LoaderManagerImpl lm = loaders[i];
+                if (lm.mRetaining) {
+                    retainLoaders = true;
+                } else {
+                    lm.doDestroy();
+                    mAllLoaderManagers.remove(lm.mWho);
                 }
             }
         }
@@ -5236,14 +5238,15 @@
         }
         mFragments.dispatchStart();
         if (mAllLoaderManagers != null) {
-            LoaderManagerImpl loaders[] = new LoaderManagerImpl[mAllLoaderManagers.size()];
-            mAllLoaderManagers.values().toArray(loaders);
-            if (loaders != null) {
-                for (int i=0; i<loaders.length; i++) {
-                    LoaderManagerImpl lm = loaders[i];
-                    lm.finishRetain();
-                    lm.doReportStart();
-                }
+            final int N = mAllLoaderManagers.size();
+            LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
+            for (int i=N-1; i>=0; i--) {
+                loaders[i] = mAllLoaderManagers.valueAt(i);
+            }
+            for (int i=0; i<N; i++) {
+                LoaderManagerImpl lm = loaders[i];
+                lm.finishRetain();
+                lm.doReportStart();
             }
         }
     }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index de94bb7..c22de0c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -16,12 +16,11 @@
 
 package android.app;
 
-import android.Manifest;
+import android.util.ArrayMap;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IAppOpsCallback;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 
 import android.content.Context;
@@ -53,8 +52,8 @@
 public class AppOpsManager {
     final Context mContext;
     final IAppOpsService mService;
-    final HashMap<Callback, IAppOpsCallback> mModeWatchers
-            = new HashMap<Callback, IAppOpsCallback>();
+    final ArrayMap<Callback, IAppOpsCallback> mModeWatchers
+            = new ArrayMap<Callback, IAppOpsCallback>();
 
     public static final int MODE_ALLOWED = 0;
     public static final int MODE_IGNORED = 1;
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index b6aeb84..6933a7a 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -26,6 +26,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.DebugUtils;
 import android.util.Log;
@@ -43,7 +44,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.HashMap;
 
 final class FragmentState implements Parcelable {
     final String mClassName;
@@ -342,8 +342,8 @@
  * the activity UI was in.
  */
 public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
-    private static final HashMap<String, Class<?>> sClassMap =
-            new HashMap<String, Class<?>>();
+    private static final ArrayMap<String, Class<?>> sClassMap =
+            new ArrayMap<String, Class<?>>();
     
     static final int INVALID_STATE = -1;   // Invalid state used as a null value.
     static final int INITIALIZING = 0;     // Not yet created.
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index 18534c6..b80a14b 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -44,8 +44,6 @@
  * you have no control over this shrinking -- if you set a capacity and then remove an
  * item, it may reduce the capacity to better match the current size.  In the future an
  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
- *
- * @hide
  */
 public final class ArrayMap<K, V> implements Map<K, V> {
     private static final boolean DEBUG = false;
@@ -86,7 +84,7 @@
             return ~0;
         }
 
-        int index = SparseArray.binarySearch(mHashes, N, hash);
+        int index = ContainerHelpers.binarySearch(mHashes, N, hash);
 
         // If the hash code wasn't found, then we have no entry for this key.
         if (index < 0) {
@@ -188,8 +186,8 @@
      * will grow once items are added to it.
      */
     public ArrayMap() {
-        mHashes = SparseArray.EMPTY_INTS;
-        mArray = SparseArray.EMPTY_OBJECTS;
+        mHashes = ContainerHelpers.EMPTY_INTS;
+        mArray = ContainerHelpers.EMPTY_OBJECTS;
         mSize = 0;
     }
 
@@ -198,8 +196,8 @@
      */
     public ArrayMap(int capacity) {
         if (capacity == 0) {
-            mHashes = SparseArray.EMPTY_INTS;
-            mArray = SparseArray.EMPTY_OBJECTS;
+            mHashes = ContainerHelpers.EMPTY_INTS;
+            mArray = ContainerHelpers.EMPTY_OBJECTS;
         } else {
             allocArrays(capacity);
         }
@@ -223,8 +221,8 @@
     public void clear() {
         if (mSize != 0) {
             freeArrays(mHashes, mArray, mSize);
-            mHashes = SparseArray.EMPTY_INTS;
-            mArray = SparseArray.EMPTY_OBJECTS;
+            mHashes = ContainerHelpers.EMPTY_INTS;
+            mArray = ContainerHelpers.EMPTY_OBJECTS;
             mSize = 0;
         }
     }
@@ -439,8 +437,8 @@
             // Now empty.
             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
             freeArrays(mHashes, mArray, mSize);
-            mHashes = SparseArray.EMPTY_INTS;
-            mArray = SparseArray.EMPTY_OBJECTS;
+            mHashes = ContainerHelpers.EMPTY_INTS;
+            mArray = ContainerHelpers.EMPTY_OBJECTS;
             mSize = 0;
         } else {
             if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index 4e1d32c..1648ec9 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -85,7 +85,7 @@
             return ~0;
         }
 
-        int index = SparseArray.binarySearch(mHashes, N, hash);
+        int index = ContainerHelpers.binarySearch(mHashes, N, hash);
 
         // If the hash code wasn't found, then we have no entry for this key.
         if (index < 0) {
@@ -187,8 +187,8 @@
      * will grow once items are added to it.
      */
     public ArraySet() {
-        mHashes = SparseArray.EMPTY_INTS;
-        mArray = SparseArray.EMPTY_OBJECTS;
+        mHashes = ContainerHelpers.EMPTY_INTS;
+        mArray = ContainerHelpers.EMPTY_OBJECTS;
         mSize = 0;
     }
 
@@ -197,8 +197,8 @@
      */
     public ArraySet(int capacity) {
         if (capacity == 0) {
-            mHashes = SparseArray.EMPTY_INTS;
-            mArray = SparseArray.EMPTY_OBJECTS;
+            mHashes = ContainerHelpers.EMPTY_INTS;
+            mArray = ContainerHelpers.EMPTY_OBJECTS;
         } else {
             allocArrays(capacity);
         }
@@ -223,8 +223,8 @@
     public void clear() {
         if (mSize != 0) {
             freeArrays(mHashes, mArray, mSize);
-            mHashes = SparseArray.EMPTY_INTS;
-            mArray = SparseArray.EMPTY_OBJECTS;
+            mHashes = ContainerHelpers.EMPTY_INTS;
+            mArray = ContainerHelpers.EMPTY_OBJECTS;
             mSize = 0;
         }
     }
@@ -371,8 +371,8 @@
             // Now empty.
             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
             freeArrays(mHashes, mArray, mSize);
-            mHashes = SparseArray.EMPTY_INTS;
-            mArray = SparseArray.EMPTY_OBJECTS;
+            mHashes = ContainerHelpers.EMPTY_INTS;
+            mArray = ContainerHelpers.EMPTY_OBJECTS;
             mSize = 0;
         } else {
             if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
diff --git a/core/java/android/util/ContainerHelpers.java b/core/java/android/util/ContainerHelpers.java
new file mode 100644
index 0000000..624c4bd
--- /dev/null
+++ b/core/java/android/util/ContainerHelpers.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+class ContainerHelpers {
+    static final boolean[] EMPTY_BOOLEANS = new boolean[0];
+    static final int[] EMPTY_INTS = new int[0];
+    static final long[] EMPTY_LONGS = new long[0];
+    static final Object[] EMPTY_OBJECTS = new Object[0];
+
+    // This is Arrays.binarySearch(), but doesn't do any argument validation.
+    static int binarySearch(int[] array, int size, int value) {
+        int lo = 0;
+        int hi = size - 1;
+
+        while (lo <= hi) {
+            final int mid = (lo + hi) >>> 1;
+            final int midVal = array[mid];
+
+            if (midVal < value) {
+                lo = mid + 1;
+            } else if (midVal > value) {
+                hi = mid - 1;
+            } else {
+                return mid;  // value found
+            }
+        }
+        return ~lo;  // value not present
+    }
+
+    static int binarySearch(long[] array, int size, long value) {
+        int lo = 0;
+        int hi = size - 1;
+
+        while (lo <= hi) {
+            final int mid = (lo + hi) >>> 1;
+            final long midVal = array[mid];
+
+            if (midVal < value) {
+                lo = mid + 1;
+            } else if (midVal > value) {
+                hi = mid - 1;
+            } else {
+                return mid;  // value found
+            }
+        }
+        return ~lo;  // value not present
+    }
+}
diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java
index 77dfcf4..63e8c25 100644
--- a/core/java/android/util/LongSparseArray.java
+++ b/core/java/android/util/LongSparseArray.java
@@ -64,8 +64,8 @@
      */
     public LongSparseArray(int initialCapacity) {
         if (initialCapacity == 0) {
-            mKeys = SparseLongArray.EMPTY_LONGS;
-            mValues = SparseArray.EMPTY_OBJECTS;
+            mKeys = ContainerHelpers.EMPTY_LONGS;
+            mValues = ContainerHelpers.EMPTY_OBJECTS;
         } else {
             initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
             mKeys = new long[initialCapacity];
@@ -102,7 +102,7 @@
      */
     @SuppressWarnings("unchecked")
     public E get(long key, E valueIfKeyNotFound) {
-        int i = binarySearch(mKeys, 0, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i < 0 || mValues[i] == DELETED) {
             return valueIfKeyNotFound;
@@ -115,7 +115,7 @@
      * Removes the mapping from the specified key, if there was any.
      */
     public void delete(long key) {
-        int i = binarySearch(mKeys, 0, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             if (mValues[i] != DELETED) {
@@ -176,7 +176,7 @@
      * was one.
      */
     public void put(long key, E value) {
-        int i = binarySearch(mKeys, 0, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             mValues[i] = value;
@@ -193,7 +193,7 @@
                 gc();
 
                 // Search again because indices may have changed.
-                i = ~binarySearch(mKeys, 0, mSize, key);
+                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
             }
 
             if (mSize >= mKeys.length) {
@@ -284,7 +284,7 @@
             gc();
         }
 
-        return binarySearch(mKeys, 0, mSize, key);
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
     }
 
     /**
@@ -356,23 +356,36 @@
         mSize = pos + 1;
     }
 
-    private static int binarySearch(long[] a, int start, int len, long key) {
-        int high = start + len, low = start - 1, guess;
-
-        while (high - low > 1) {
-            guess = (high + low) / 2;
-
-            if (a[guess] < key)
-                low = guess;
-            else
-                high = guess;
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings. If
+     * this map contains itself as a value, the string "(this Map)"
+     * will appear in its place.
+     */
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "{}";
         }
 
-        if (high == start + len)
-            return ~(start + len);
-        else if (a[high] == key)
-            return high;
-        else
-            return ~high;
+        StringBuilder buffer = new StringBuilder(mSize * 28);
+        buffer.append('{');
+        for (int i=0; i<mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            long key = keyAt(i);
+            buffer.append(key);
+            buffer.append('=');
+            Object value = valueAt(i);
+            if (value != this) {
+                buffer.append(value);
+            } else {
+                buffer.append("(this Map)");
+            }
+        }
+        buffer.append('}');
+        return buffer.toString();
     }
 }
diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java
index d795308..133e415 100644
--- a/core/java/android/util/LongSparseLongArray.java
+++ b/core/java/android/util/LongSparseLongArray.java
@@ -58,8 +58,8 @@
      */
     public LongSparseLongArray(int initialCapacity) {
         if (initialCapacity == 0) {
-            mKeys = SparseLongArray.EMPTY_LONGS;
-            mValues = SparseLongArray.EMPTY_LONGS;
+            mKeys = ContainerHelpers.EMPTY_LONGS;
+            mValues = ContainerHelpers.EMPTY_LONGS;
         } else {
             initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
             mKeys = new long[initialCapacity];
@@ -94,7 +94,7 @@
      * if no such mapping has been made.
      */
     public long get(long key, long valueIfKeyNotFound) {
-        int i = Arrays.binarySearch(mKeys, 0, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i < 0) {
             return valueIfKeyNotFound;
@@ -107,7 +107,7 @@
      * Removes the mapping from the specified key, if there was any.
      */
     public void delete(long key) {
-        int i = Arrays.binarySearch(mKeys, 0, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             removeAt(i);
@@ -129,7 +129,7 @@
      * was one.
      */
     public void put(long key, long value) {
-        int i = Arrays.binarySearch(mKeys, 0, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             mValues[i] = value;
@@ -183,7 +183,7 @@
      * key is not mapped.
      */
     public int indexOfKey(long key) {
-        return Arrays.binarySearch(mKeys, 0, mSize, key);
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
     }
 
     /**
@@ -241,4 +241,31 @@
         mKeys = nkeys;
         mValues = nvalues;
     }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings.
+     */
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "{}";
+        }
+
+        StringBuilder buffer = new StringBuilder(mSize * 28);
+        buffer.append('{');
+        for (int i=0; i<mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            long key = keyAt(i);
+            buffer.append(key);
+            buffer.append('=');
+            long value = valueAt(i);
+            buffer.append(value);
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
 }
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 0e013c3..6e66090 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -42,8 +42,6 @@
  */
 public class SparseArray<E> implements Cloneable {
     private static final Object DELETED = new Object();
-    static final int[] EMPTY_INTS = new int[0];
-    static final Object[] EMPTY_OBJECTS = new Object[0];
     private boolean mGarbage = false;
 
     private int[] mKeys;
@@ -66,8 +64,8 @@
      */
     public SparseArray(int initialCapacity) {
         if (initialCapacity == 0) {
-            mKeys = EMPTY_INTS;
-            mValues = EMPTY_OBJECTS;
+            mKeys = ContainerHelpers.EMPTY_INTS;
+            mValues = ContainerHelpers.EMPTY_OBJECTS;
         } else {
             initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
             mKeys = new int[initialCapacity];
@@ -104,7 +102,7 @@
      */
     @SuppressWarnings("unchecked")
     public E get(int key, E valueIfKeyNotFound) {
-        int i = binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i < 0 || mValues[i] == DELETED) {
             return valueIfKeyNotFound;
@@ -117,7 +115,7 @@
      * Removes the mapping from the specified key, if there was any.
      */
     public void delete(int key) {
-        int i = binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             if (mValues[i] != DELETED) {
@@ -144,6 +142,19 @@
         }
     }
 
+    /**
+     * Remove a range of mappings as a batch.
+     *
+     * @param index Index to begin at
+     * @param size Number of mappings to remove
+     */
+    public void removeAtRange(int index, int size) {
+        final int end = Math.min(mSize, index + size);
+        for (int i = index; i < end; i++) {
+            removeAt(i);
+        }
+    }
+
     private void gc() {
         // Log.e("SparseArray", "gc start with " + mSize);
 
@@ -178,7 +189,7 @@
      * was one.
      */
     public void put(int key, E value) {
-        int i = binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             mValues[i] = value;
@@ -195,7 +206,7 @@
                 gc();
 
                 // Search again because indices may have changed.
-                i = ~binarySearch(mKeys, mSize, key);
+                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
             }
 
             if (mSize >= mKeys.length) {
@@ -286,7 +297,7 @@
             gc();
         }
 
-        return binarySearch(mKeys, mSize, key);
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
     }
 
     /**
@@ -360,23 +371,36 @@
         mSize = pos + 1;
     }
 
-    // This is Arrays.binarySearch(), but doesn't do any argument validation.
-    static int binarySearch(int[] array, int size, int value) {
-        int lo = 0;
-        int hi = size - 1;
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings. If
+     * this map contains itself as a value, the string "(this Map)"
+     * will appear in its place.
+     */
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "{}";
+        }
 
-        while (lo <= hi) {
-            int mid = (lo + hi) >>> 1;
-            int midVal = array[mid];
-
-            if (midVal < value) {
-                lo = mid + 1;
-            } else if (midVal > value) {
-                hi = mid - 1;
+        StringBuilder buffer = new StringBuilder(mSize * 28);
+        buffer.append('{');
+        for (int i=0; i<mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            int key = keyAt(i);
+            buffer.append(key);
+            buffer.append('=');
+            Object value = valueAt(i);
+            if (value != this) {
+                buffer.append(value);
             } else {
-                return mid;  // value found
+                buffer.append("(this Map)");
             }
         }
-        return ~lo;  // value not present
+        buffer.append('}');
+        return buffer.toString();
     }
 }
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index 430755a..da196d7 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -35,8 +35,6 @@
  * the performance difference is not significant, less than 50%.</p>
  */
 public class SparseBooleanArray implements Cloneable {
-    static final boolean[] EMPTY_BOOLEANS = new boolean[0];
-
     /**
      * Creates a new SparseBooleanArray containing no mappings.
      */
@@ -53,8 +51,8 @@
      */
     public SparseBooleanArray(int initialCapacity) {
         if (initialCapacity == 0) {
-            mKeys = SparseArray.EMPTY_INTS;
-            mValues = EMPTY_BOOLEANS;
+            mKeys = ContainerHelpers.EMPTY_INTS;
+            mValues = ContainerHelpers.EMPTY_BOOLEANS;
         } else {
             initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
             mKeys = new int[initialCapacity];
@@ -89,7 +87,7 @@
      * if no such mapping has been made.
      */
     public boolean get(int key, boolean valueIfKeyNotFound) {
-        int i = SparseArray.binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i < 0) {
             return valueIfKeyNotFound;
@@ -102,7 +100,7 @@
      * Removes the mapping from the specified key, if there was any.
      */
     public void delete(int key) {
-        int i = SparseArray.binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1));
@@ -117,7 +115,7 @@
      * was one.
      */
     public void put(int key, boolean value) {
-        int i = SparseArray.binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             mValues[i] = value;
@@ -182,7 +180,7 @@
      * key is not mapped.
      */
     public int indexOfKey(int key) {
-        return SparseArray.binarySearch(mKeys, mSize, key);
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
     }
 
     /**
@@ -237,7 +235,34 @@
         mValues[pos] = value;
         mSize = pos + 1;
     }
-    
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings.
+     */
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "{}";
+        }
+
+        StringBuilder buffer = new StringBuilder(mSize * 28);
+        buffer.append('{');
+        for (int i=0; i<mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            int key = keyAt(i);
+            buffer.append(key);
+            buffer.append('=');
+            boolean value = valueAt(i);
+            buffer.append(value);
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+
     private int[] mKeys;
     private boolean[] mValues;
     private int mSize;
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index b6fb295..c2bacb0 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -54,8 +54,8 @@
      */
     public SparseIntArray(int initialCapacity) {
         if (initialCapacity == 0) {
-            mKeys = SparseArray.EMPTY_INTS;
-            mValues = SparseArray.EMPTY_INTS;
+            mKeys = ContainerHelpers.EMPTY_INTS;
+            mValues = ContainerHelpers.EMPTY_INTS;
         } else {
             initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
             mKeys = new int[initialCapacity];
@@ -90,7 +90,7 @@
      * if no such mapping has been made.
      */
     public int get(int key, int valueIfKeyNotFound) {
-        int i = SparseArray.binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i < 0) {
             return valueIfKeyNotFound;
@@ -103,7 +103,7 @@
      * Removes the mapping from the specified key, if there was any.
      */
     public void delete(int key) {
-        int i = SparseArray.binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             removeAt(i);
@@ -125,7 +125,7 @@
      * was one.
      */
     public void put(int key, int value) {
-        int i = SparseArray.binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             mValues[i] = value;
@@ -190,7 +190,7 @@
      * key is not mapped.
      */
     public int indexOfKey(int key) {
-        return SparseArray.binarySearch(mKeys, mSize, key);
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
     }
 
     /**
@@ -245,4 +245,31 @@
         mValues[pos] = value;
         mSize = pos + 1;
     }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings.
+     */
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "{}";
+        }
+
+        StringBuilder buffer = new StringBuilder(mSize * 28);
+        buffer.append('{');
+        for (int i=0; i<mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            int key = keyAt(i);
+            buffer.append(key);
+            buffer.append('=');
+            int value = valueAt(i);
+            buffer.append(value);
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
 }
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index 55cb3a2..182fd35 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -34,8 +34,6 @@
  * the performance difference is not significant, less than 50%.</p>
  */
 public class SparseLongArray implements Cloneable {
-    static final long[] EMPTY_LONGS = new long[0];
-
     private int[] mKeys;
     private long[] mValues;
     private int mSize;
@@ -56,8 +54,8 @@
      */
     public SparseLongArray(int initialCapacity) {
         if (initialCapacity == 0) {
-            mKeys = SparseArray.EMPTY_INTS;
-            mValues = EMPTY_LONGS;
+            mKeys = ContainerHelpers.EMPTY_INTS;
+            mValues = ContainerHelpers.EMPTY_LONGS;
         } else {
             initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
             mKeys = new int[initialCapacity];
@@ -92,7 +90,7 @@
      * if no such mapping has been made.
      */
     public long get(int key, long valueIfKeyNotFound) {
-        int i = SparseArray.binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i < 0) {
             return valueIfKeyNotFound;
@@ -105,7 +103,7 @@
      * Removes the mapping from the specified key, if there was any.
      */
     public void delete(int key) {
-        int i = SparseArray.binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             removeAt(i);
@@ -127,7 +125,7 @@
      * was one.
      */
     public void put(int key, long value) {
-        int i = SparseArray.binarySearch(mKeys, mSize, key);
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
         if (i >= 0) {
             mValues[i] = value;
@@ -181,7 +179,7 @@
      * key is not mapped.
      */
     public int indexOfKey(int key) {
-        return SparseArray.binarySearch(mKeys, mSize, key);
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
     }
 
     /**
@@ -239,4 +237,31 @@
         mKeys = nkeys;
         mValues = nvalues;
     }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings.
+     */
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "{}";
+        }
+
+        StringBuilder buffer = new StringBuilder(mSize * 28);
+        buffer.append('{');
+        for (int i=0; i<mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            int key = keyAt(i);
+            buffer.append(key);
+            buffer.append('=');
+            long value = valueAt(i);
+            buffer.append(value);
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
 }