Automatic sources dropoff on 2020-06-10 18:32:38.095721

The change is generated with prebuilt drop tool.

Change-Id: I24cbf6ba6db262a1ae1445db1427a08fee35b3b4
diff --git a/android/util/AndroidException.java b/android/util/AndroidException.java
new file mode 100644
index 0000000..1345ddf
--- /dev/null
+++ b/android/util/AndroidException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * Base class for all checked exceptions thrown by the Android frameworks.
+ */
+public class AndroidException extends Exception {
+    public AndroidException() {
+    }
+
+    public AndroidException(String name) {
+        super(name);
+    }
+
+    public AndroidException(String name, Throwable cause) {
+        super(name, cause);
+    }
+
+    public AndroidException(Exception cause) {
+        super(cause);
+    }
+
+    /** @hide */
+    protected AndroidException(String message, Throwable cause, boolean enableSuppression,
+            boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+};
+
diff --git a/android/util/AndroidRuntimeException.java b/android/util/AndroidRuntimeException.java
new file mode 100644
index 0000000..2b824bf
--- /dev/null
+++ b/android/util/AndroidRuntimeException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * Base class for all unchecked exceptions thrown by the Android frameworks.
+ */
+public class AndroidRuntimeException extends RuntimeException {
+    public AndroidRuntimeException() {
+    }
+
+    public AndroidRuntimeException(String name) {
+        super(name);
+    }
+
+    public AndroidRuntimeException(String name, Throwable cause) {
+        super(name, cause);
+    }
+
+    public AndroidRuntimeException(Exception cause) {
+        super(cause);
+    }
+};
+
diff --git a/android/util/ArrayMap.java b/android/util/ArrayMap.java
new file mode 100644
index 0000000..4cf0a36
--- /dev/null
+++ b/android/util/ArrayMap.java
@@ -0,0 +1,1056 @@
+/*
+ * 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.util.EmptyArray;
+
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ArrayMap is a generic key->value mapping data structure that is
+ * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
+ * It keeps its mappings in an array data structure -- an integer array of hash
+ * codes for each item, and an Object array of the key/value pairs.  This allows it to
+ * avoid having to create an extra object for every entry put in to the map, and it
+ * also tries to control the growth of the size of these arrays more aggressively
+ * (since growing them only requires copying the entries in the array, not rebuilding
+ * a hash map).
+ *
+ * <p>Note that this implementation is not intended to be appropriate for data structures
+ * that may contain large numbers of items.  It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array.  For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>Because this container is intended to better balance memory use, unlike most other
+ * standard Java containers it will shrink its array as items are removed from it.  Currently
+ * 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>
+ *
+ * <p>This structure is <b>NOT</b> thread-safe.</p>
+ */
+public final class ArrayMap<K, V> implements Map<K, V> {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "ArrayMap";
+
+    /**
+     * Attempt to spot concurrent modifications to this data structure.
+     *
+     * It's best-effort, but any time we can throw something more diagnostic than an
+     * ArrayIndexOutOfBoundsException deep in the ArrayMap internals it's going to
+     * save a lot of development time.
+     *
+     * Good times to look for CME include after any allocArrays() call and at the end of
+     * functions that change mSize (put/remove/clear).
+     */
+    private static final boolean CONCURRENT_MODIFICATION_EXCEPTIONS = true;
+
+    /**
+     * The minimum amount by which the capacity of a ArrayMap will increase.
+     * This is tuned to be relatively space-efficient.
+     */
+    private static final int BASE_SIZE = 4;
+
+    /**
+     * Maximum number of entries to have in array caches.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    private static final int CACHE_SIZE = 10;
+
+    /**
+     * Special hash array value that indicates the container is immutable.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
+
+    /**
+     * @hide Special immutable empty ArrayMap.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use your own singleton empty map.
+    public static final ArrayMap EMPTY = new ArrayMap<>(-1);
+
+    /**
+     * Caches of small array objects to avoid spamming garbage.  The cache
+     * Object[] variable is a pointer to a linked list of array objects.
+     * The first entry in the array is a pointer to the next array in the
+     * list; the second entry is a pointer to the int[] hash code array for it.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    static Object[] mBaseCache;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    static int mBaseCacheSize;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    static Object[] mTwiceBaseCache;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    static int mTwiceBaseCacheSize;
+    /**
+     * Separate locks for each cache since each can be accessed independently of the other without
+     * risk of a deadlock.
+     */
+    private static final Object sBaseCacheLock = new Object();
+    private static final Object sTwiceBaseCacheLock = new Object();
+
+    private final boolean mIdentityHashCode;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API.
+    int[] mHashes;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API.
+    Object[] mArray;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
+    int mSize;
+    private MapCollections<K, V> mCollections;
+
+    private static int binarySearchHashes(int[] hashes, int N, int hash) {
+        try {
+            return ContainerHelpers.binarySearch(hashes, N, hash);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
+                throw new ConcurrentModificationException();
+            } else {
+                throw e; // the cache is poisoned at this point, there's not much we can do
+            }
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
+    int indexOf(Object key, int hash) {
+        final int N = mSize;
+
+        // Important fast case: if nothing is in here, nothing to look for.
+        if (N == 0) {
+            return ~0;
+        }
+
+        int index = binarySearchHashes(mHashes, N, hash);
+
+        // If the hash code wasn't found, then we have no entry for this key.
+        if (index < 0) {
+            return index;
+        }
+
+        // If the key at the returned index matches, that's what we want.
+        if (key.equals(mArray[index<<1])) {
+            return index;
+        }
+
+        // Search for a matching key after the index.
+        int end;
+        for (end = index + 1; end < N && mHashes[end] == hash; end++) {
+            if (key.equals(mArray[end << 1])) return end;
+        }
+
+        // Search for a matching key before the index.
+        for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
+            if (key.equals(mArray[i << 1])) return i;
+        }
+
+        // Key not found -- return negative value indicating where a
+        // new entry for this key should go.  We use the end of the
+        // hash chain to reduce the number of array entries that will
+        // need to be copied when inserting.
+        return ~end;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null)
+    int indexOfNull() {
+        final int N = mSize;
+
+        // Important fast case: if nothing is in here, nothing to look for.
+        if (N == 0) {
+            return ~0;
+        }
+
+        int index = binarySearchHashes(mHashes, N, 0);
+
+        // If the hash code wasn't found, then we have no entry for this key.
+        if (index < 0) {
+            return index;
+        }
+
+        // If the key at the returned index matches, that's what we want.
+        if (null == mArray[index<<1]) {
+            return index;
+        }
+
+        // Search for a matching key after the index.
+        int end;
+        for (end = index + 1; end < N && mHashes[end] == 0; end++) {
+            if (null == mArray[end << 1]) return end;
+        }
+
+        // Search for a matching key before the index.
+        for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
+            if (null == mArray[i << 1]) return i;
+        }
+
+        // Key not found -- return negative value indicating where a
+        // new entry for this key should go.  We use the end of the
+        // hash chain to reduce the number of array entries that will
+        // need to be copied when inserting.
+        return ~end;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    private void allocArrays(final int size) {
+        if (mHashes == EMPTY_IMMUTABLE_INTS) {
+            throw new UnsupportedOperationException("ArrayMap is immutable");
+        }
+        if (size == (BASE_SIZE*2)) {
+            synchronized (sTwiceBaseCacheLock) {
+                if (mTwiceBaseCache != null) {
+                    final Object[] array = mTwiceBaseCache;
+                    mArray = array;
+                    try {
+                        mTwiceBaseCache = (Object[]) array[0];
+                        mHashes = (int[]) array[1];
+                        if (mHashes != null) {
+                            array[0] = array[1] = null;
+                            mTwiceBaseCacheSize--;
+                            if (DEBUG) {
+                                Log.d(TAG, "Retrieving 2x cache " + mHashes
+                                        + " now have " + mTwiceBaseCacheSize + " entries");
+                            }
+                            return;
+                        }
+                    } catch (ClassCastException e) {
+                    }
+                    // Whoops!  Someone trampled the array (probably due to not protecting
+                    // their access with a lock).  Our cache is corrupt; report and give up.
+                    Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
+                            + " [1]=" + array[1]);
+                    mTwiceBaseCache = null;
+                    mTwiceBaseCacheSize = 0;
+                }
+            }
+        } else if (size == BASE_SIZE) {
+            synchronized (sBaseCacheLock) {
+                if (mBaseCache != null) {
+                    final Object[] array = mBaseCache;
+                    mArray = array;
+                    try {
+                        mBaseCache = (Object[]) array[0];
+                        mHashes = (int[]) array[1];
+                        if (mHashes != null) {
+                            array[0] = array[1] = null;
+                            mBaseCacheSize--;
+                            if (DEBUG) {
+                                Log.d(TAG, "Retrieving 1x cache " + mHashes
+                                        + " now have " + mBaseCacheSize + " entries");
+                            }
+                            return;
+                        }
+                    } catch (ClassCastException e) {
+                    }
+                    // Whoops!  Someone trampled the array (probably due to not protecting
+                    // their access with a lock).  Our cache is corrupt; report and give up.
+                    Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
+                            + " [1]=" + array[1]);
+                    mBaseCache = null;
+                    mBaseCacheSize = 0;
+                }
+            }
+        }
+
+        mHashes = new int[size];
+        mArray = new Object[size<<1];
+    }
+
+    /**
+     * Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other
+     * words, don't pass mHashes or mArray in directly.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
+        if (hashes.length == (BASE_SIZE*2)) {
+            synchronized (sTwiceBaseCacheLock) {
+                if (mTwiceBaseCacheSize < CACHE_SIZE) {
+                    array[0] = mTwiceBaseCache;
+                    array[1] = hashes;
+                    for (int i=(size<<1)-1; i>=2; i--) {
+                        array[i] = null;
+                    }
+                    mTwiceBaseCache = array;
+                    mTwiceBaseCacheSize++;
+                    if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
+                            + " now have " + mTwiceBaseCacheSize + " entries");
+                }
+            }
+        } else if (hashes.length == BASE_SIZE) {
+            synchronized (sBaseCacheLock) {
+                if (mBaseCacheSize < CACHE_SIZE) {
+                    array[0] = mBaseCache;
+                    array[1] = hashes;
+                    for (int i=(size<<1)-1; i>=2; i--) {
+                        array[i] = null;
+                    }
+                    mBaseCache = array;
+                    mBaseCacheSize++;
+                    if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
+                            + " now have " + mBaseCacheSize + " entries");
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a new empty ArrayMap.  The default capacity of an array map is 0, and
+     * will grow once items are added to it.
+     */
+    public ArrayMap() {
+        this(0, false);
+    }
+
+    /**
+     * Create a new ArrayMap with a given initial capacity.
+     */
+    public ArrayMap(int capacity) {
+        this(capacity, false);
+    }
+
+    /** {@hide} */
+    public ArrayMap(int capacity, boolean identityHashCode) {
+        mIdentityHashCode = identityHashCode;
+
+        // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
+        // instance instead of the usual EmptyArray.INT. The reference
+        // is checked later to see if the array is allowed to grow.
+        if (capacity < 0) {
+            mHashes = EMPTY_IMMUTABLE_INTS;
+            mArray = EmptyArray.OBJECT;
+        } else if (capacity == 0) {
+            mHashes = EmptyArray.INT;
+            mArray = EmptyArray.OBJECT;
+        } else {
+            allocArrays(capacity);
+        }
+        mSize = 0;
+    }
+
+    /**
+     * Create a new ArrayMap with the mappings from the given ArrayMap.
+     */
+    public ArrayMap(ArrayMap<K, V> map) {
+        this();
+        if (map != null) {
+            putAll(map);
+        }
+    }
+
+    /**
+     * Make the array map empty.  All storage is released.
+     */
+    @Override
+    public void clear() {
+        if (mSize > 0) {
+            final int[] ohashes = mHashes;
+            final Object[] oarray = mArray;
+            final int osize = mSize;
+            mHashes = EmptyArray.INT;
+            mArray = EmptyArray.OBJECT;
+            mSize = 0;
+            freeArrays(ohashes, oarray, osize);
+        }
+        if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize > 0) {
+            throw new ConcurrentModificationException();
+        }
+    }
+
+    /**
+     * @hide
+     * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
+     */
+    public void erase() {
+        if (mSize > 0) {
+            final int N = mSize<<1;
+            final Object[] array = mArray;
+            for (int i=0; i<N; i++) {
+                array[i] = null;
+            }
+            mSize = 0;
+        }
+    }
+
+    /**
+     * Ensure the array map can hold at least <var>minimumCapacity</var>
+     * items.
+     */
+    public void ensureCapacity(int minimumCapacity) {
+        final int osize = mSize;
+        if (mHashes.length < minimumCapacity) {
+            final int[] ohashes = mHashes;
+            final Object[] oarray = mArray;
+            allocArrays(minimumCapacity);
+            if (mSize > 0) {
+                System.arraycopy(ohashes, 0, mHashes, 0, osize);
+                System.arraycopy(oarray, 0, mArray, 0, osize<<1);
+            }
+            freeArrays(ohashes, oarray, osize);
+        }
+        if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize != osize) {
+            throw new ConcurrentModificationException();
+        }
+    }
+
+    /**
+     * Check whether a key exists in the array.
+     *
+     * @param key The key to search for.
+     * @return Returns true if the key exists, else false.
+     */
+    @Override
+    public boolean containsKey(Object key) {
+        return indexOfKey(key) >= 0;
+    }
+
+    /**
+     * Returns the index of a key in the set.
+     *
+     * @param key The key to search for.
+     * @return Returns the index of the key if it exists, else a negative integer.
+     */
+    public int indexOfKey(Object key) {
+        return key == null ? indexOfNull()
+                : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified value, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(Object value) {
+        final int N = mSize*2;
+        final Object[] array = mArray;
+        if (value == null) {
+            for (int i=1; i<N; i+=2) {
+                if (array[i] == null) {
+                    return i>>1;
+                }
+            }
+        } else {
+            for (int i=1; i<N; i+=2) {
+                if (value.equals(array[i])) {
+                    return i>>1;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Check whether a value exists in the array.  This requires a linear search
+     * through the entire array.
+     *
+     * @param value The value to search for.
+     * @return Returns true if the value exists, else false.
+     */
+    @Override
+    public boolean containsValue(Object value) {
+        return indexOfValue(value) >= 0;
+    }
+
+    /**
+     * Retrieve a value from the array.
+     * @param key The key of the value to retrieve.
+     * @return Returns the value associated with the given key,
+     * or null if there is no such key.
+     */
+    @Override
+    public V get(Object key) {
+        final int index = indexOfKey(key);
+        return index >= 0 ? (V)mArray[(index<<1)+1] : null;
+    }
+
+    /**
+     * Return the key at the given index in the array.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     *
+     * @param index The desired index, must be between 0 and {@link #size()}-1.
+     * @return Returns the key stored at the given index.
+     */
+    public K keyAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return (K)mArray[index << 1];
+    }
+
+    /**
+     * Return the value at the given index in the array.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     *
+     * @param index The desired index, must be between 0 and {@link #size()}-1.
+     * @return Returns the value stored at the given index.
+     */
+    public V valueAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return (V)mArray[(index << 1) + 1];
+    }
+
+    /**
+     * Set the value at a given index in the array.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     *
+     * @param index The desired index, must be between 0 and {@link #size()}-1.
+     * @param value The new value to store at this index.
+     * @return Returns the previous value at the given index.
+     */
+    public V setValueAt(int index, V value) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        index = (index << 1) + 1;
+        V old = (V)mArray[index];
+        mArray[index] = value;
+        return old;
+    }
+
+    /**
+     * Return true if the array map contains no items.
+     */
+    @Override
+    public boolean isEmpty() {
+        return mSize <= 0;
+    }
+
+    /**
+     * Add a new value to the array map.
+     * @param key The key under which to store the value.  If
+     * this key already exists in the array, its value will be replaced.
+     * @param value The value to store for the given key.
+     * @return Returns the old value that was stored for the given key, or null if there
+     * was no such key.
+     */
+    @Override
+    public V put(K key, V value) {
+        final int osize = mSize;
+        final int hash;
+        int index;
+        if (key == null) {
+            hash = 0;
+            index = indexOfNull();
+        } else {
+            hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
+            index = indexOf(key, hash);
+        }
+        if (index >= 0) {
+            index = (index<<1) + 1;
+            final V old = (V)mArray[index];
+            mArray[index] = value;
+            return old;
+        }
+
+        index = ~index;
+        if (osize >= mHashes.length) {
+            final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
+                    : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
+
+            if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
+
+            final int[] ohashes = mHashes;
+            final Object[] oarray = mArray;
+            allocArrays(n);
+
+            if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
+                throw new ConcurrentModificationException();
+            }
+
+            if (mHashes.length > 0) {
+                if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
+                System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
+                System.arraycopy(oarray, 0, mArray, 0, oarray.length);
+            }
+
+            freeArrays(ohashes, oarray, osize);
+        }
+
+        if (index < osize) {
+            if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)
+                    + " to " + (index+1));
+            System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
+            System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
+        }
+
+        if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
+            if (osize != mSize || index >= mHashes.length) {
+                throw new ConcurrentModificationException();
+            }
+        }
+        mHashes[index] = hash;
+        mArray[index<<1] = key;
+        mArray[(index<<1)+1] = value;
+        mSize++;
+        return null;
+    }
+
+    /**
+     * Special fast path for appending items to the end of the array without validation.
+     * The array must already be large enough to contain the item.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use put(K, V).
+    public void append(K key, V value) {
+        int index = mSize;
+        final int hash = key == null ? 0
+                : (mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
+        if (index >= mHashes.length) {
+            throw new IllegalStateException("Array is full");
+        }
+        if (index > 0 && mHashes[index-1] > hash) {
+            RuntimeException e = new RuntimeException("here");
+            e.fillInStackTrace();
+            Log.w(TAG, "New hash " + hash
+                    + " is before end of array hash " + mHashes[index-1]
+                    + " at index " + index + " key " + key, e);
+            put(key, value);
+            return;
+        }
+        mSize = index+1;
+        mHashes[index] = hash;
+        index <<= 1;
+        mArray[index] = key;
+        mArray[index+1] = value;
+    }
+
+    /**
+     * The use of the {@link #append} function can result in invalid array maps, in particular
+     * an array map where the same key appears multiple times.  This function verifies that
+     * the array map is valid, throwing IllegalArgumentException if a problem is found.  The
+     * main use for this method is validating an array map after unpacking from an IPC, to
+     * protect against malicious callers.
+     * @hide
+     */
+    public void validate() {
+        final int N = mSize;
+        if (N <= 1) {
+            // There can't be dups.
+            return;
+        }
+        int basehash = mHashes[0];
+        int basei = 0;
+        for (int i=1; i<N; i++) {
+            int hash = mHashes[i];
+            if (hash != basehash) {
+                basehash = hash;
+                basei = i;
+                continue;
+            }
+            // We are in a run of entries with the same hash code.  Go backwards through
+            // the array to see if any keys are the same.
+            final Object cur = mArray[i<<1];
+            for (int j=i-1; j>=basei; j--) {
+                final Object prev = mArray[j<<1];
+                if (cur == prev) {
+                    throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
+                }
+                if (cur != null && prev != null && cur.equals(prev)) {
+                    throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
+                }
+            }
+        }
+    }
+
+    /**
+     * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
+     * @param array The array whose contents are to be retrieved.
+     */
+    public void putAll(ArrayMap<? extends K, ? extends V> array) {
+        final int N = array.mSize;
+        ensureCapacity(mSize + N);
+        if (mSize == 0) {
+            if (N > 0) {
+                System.arraycopy(array.mHashes, 0, mHashes, 0, N);
+                System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
+                mSize = N;
+            }
+        } else {
+            for (int i=0; i<N; i++) {
+                put(array.keyAt(i), array.valueAt(i));
+            }
+        }
+    }
+
+    /**
+     * Remove an existing key from the array map.
+     * @param key The key of the mapping to remove.
+     * @return Returns the value that was stored under the key, or null if there
+     * was no such key.
+     */
+    @Override
+    public V remove(Object key) {
+        final int index = indexOfKey(key);
+        if (index >= 0) {
+            return removeAt(index);
+        }
+
+        return null;
+    }
+
+    /**
+     * Remove the key/value mapping at the given index.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     *
+     * @param index The desired index, must be between 0 and {@link #size()}-1.
+     * @return Returns the value that was stored at this index.
+     */
+    public V removeAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+
+        final Object old = mArray[(index << 1) + 1];
+        final int osize = mSize;
+        final int nsize;
+        if (osize <= 1) {
+            // Now empty.
+            if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
+            final int[] ohashes = mHashes;
+            final Object[] oarray = mArray;
+            mHashes = EmptyArray.INT;
+            mArray = EmptyArray.OBJECT;
+            freeArrays(ohashes, oarray, osize);
+            nsize = 0;
+        } else {
+            nsize = osize - 1;
+            if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
+                // Shrunk enough to reduce size of arrays.  We don't allow it to
+                // shrink smaller than (BASE_SIZE*2) to avoid flapping between
+                // that and BASE_SIZE.
+                final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
+
+                if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
+
+                final int[] ohashes = mHashes;
+                final Object[] oarray = mArray;
+                allocArrays(n);
+
+                if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
+                    throw new ConcurrentModificationException();
+                }
+
+                if (index > 0) {
+                    if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
+                    System.arraycopy(ohashes, 0, mHashes, 0, index);
+                    System.arraycopy(oarray, 0, mArray, 0, index << 1);
+                }
+                if (index < nsize) {
+                    if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize
+                            + " to " + index);
+                    System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
+                    System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
+                            (nsize - index) << 1);
+                }
+            } else {
+                if (index < nsize) {
+                    if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize
+                            + " to " + index);
+                    System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
+                    System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
+                            (nsize - index) << 1);
+                }
+                mArray[nsize << 1] = null;
+                mArray[(nsize << 1) + 1] = null;
+            }
+        }
+        if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
+            throw new ConcurrentModificationException();
+        }
+        mSize = nsize;
+        return (V)old;
+    }
+
+    /**
+     * Return the number of items in this array map.
+     */
+    @Override
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation returns false if the object is not a map, or
+     * if the maps have different sizes. Otherwise, for each key in this map,
+     * values of both maps are compared. If the values for any key are not
+     * equal, the method returns false, otherwise it returns true.
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object instanceof Map) {
+            Map<?, ?> map = (Map<?, ?>) object;
+            if (size() != map.size()) {
+                return false;
+            }
+
+            try {
+                for (int i=0; i<mSize; i++) {
+                    K key = keyAt(i);
+                    V mine = valueAt(i);
+                    Object theirs = map.get(key);
+                    if (mine == null) {
+                        if (theirs != null || !map.containsKey(key)) {
+                            return false;
+                        }
+                    } else if (!mine.equals(theirs)) {
+                        return false;
+                    }
+                }
+            } catch (NullPointerException ignored) {
+                return false;
+            } catch (ClassCastException ignored) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        final int[] hashes = mHashes;
+        final Object[] array = mArray;
+        int result = 0;
+        for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
+            Object value = array[v];
+            result += hashes[i] ^ (value == null ? 0 : value.hashCode());
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings. If
+     * this map contains itself as a key or a value, the string "(this Map)"
+     * will appear in its place.
+     */
+    @Override
+    public String toString() {
+        if (isEmpty()) {
+            return "{}";
+        }
+
+        StringBuilder buffer = new StringBuilder(mSize * 28);
+        buffer.append('{');
+        for (int i=0; i<mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            Object key = keyAt(i);
+            if (key != this) {
+                buffer.append(key);
+            } else {
+                buffer.append("(this Map)");
+            }
+            buffer.append('=');
+            Object value = valueAt(i);
+            if (value != this) {
+                buffer.append(ArrayUtils.deepToString(value));
+            } else {
+                buffer.append("(this Map)");
+            }
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+
+    // ------------------------------------------------------------------------
+    // Interop with traditional Java containers.  Not as efficient as using
+    // specialized collection APIs.
+    // ------------------------------------------------------------------------
+
+    private MapCollections<K, V> getCollection() {
+        if (mCollections == null) {
+            mCollections = new MapCollections<K, V>() {
+                @Override
+                protected int colGetSize() {
+                    return mSize;
+                }
+
+                @Override
+                protected Object colGetEntry(int index, int offset) {
+                    return mArray[(index<<1) + offset];
+                }
+
+                @Override
+                protected int colIndexOfKey(Object key) {
+                    return indexOfKey(key);
+                }
+
+                @Override
+                protected int colIndexOfValue(Object value) {
+                    return indexOfValue(value);
+                }
+
+                @Override
+                protected Map<K, V> colGetMap() {
+                    return ArrayMap.this;
+                }
+
+                @Override
+                protected void colPut(K key, V value) {
+                    put(key, value);
+                }
+
+                @Override
+                protected V colSetValue(int index, V value) {
+                    return setValueAt(index, value);
+                }
+
+                @Override
+                protected void colRemoveAt(int index) {
+                    removeAt(index);
+                }
+
+                @Override
+                protected void colClear() {
+                    clear();
+                }
+            };
+        }
+        return mCollections;
+    }
+
+    /**
+     * Determine if the array map contains all of the keys in the given collection.
+     * @param collection The collection whose contents are to be checked against.
+     * @return Returns true if this array map contains a key for every entry
+     * in <var>collection</var>, else returns false.
+     */
+    public boolean containsAll(Collection<?> collection) {
+        return MapCollections.containsAllHelper(this, collection);
+    }
+
+    /**
+     * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
+     * @param map The map whose contents are to be retrieved.
+     */
+    @Override
+    public void putAll(Map<? extends K, ? extends V> map) {
+        ensureCapacity(mSize + map.size());
+        for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+
+    /**
+     * Remove all keys in the array map that exist in the given collection.
+     * @param collection The collection whose contents are to be used to remove keys.
+     * @return Returns true if any keys were removed from the array map, else false.
+     */
+    public boolean removeAll(Collection<?> collection) {
+        return MapCollections.removeAllHelper(this, collection);
+    }
+
+    /**
+     * Remove all keys in the array map that do <b>not</b> exist in the given collection.
+     * @param collection The collection whose contents are to be used to determine which
+     * keys to keep.
+     * @return Returns true if any keys were removed from the array map, else false.
+     */
+    public boolean retainAll(Collection<?> collection) {
+        return MapCollections.retainAllHelper(this, collection);
+    }
+
+    /**
+     * Return a {@link java.util.Set} for iterating over and interacting with all mappings
+     * in the array map.
+     *
+     * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
+     * requires generating a number of temporary objects and allocates additional state
+     * information associated with the container that will remain for the life of the container.</p>
+     *
+     * <p><b>Note:</b></p> the semantics of this
+     * Set are subtly different than that of a {@link java.util.HashMap}: most important,
+     * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
+     * object that exists for the entire iterator, so you can <b>not</b> hold on to it
+     * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
+     */
+    @Override
+    public Set<Map.Entry<K, V>> entrySet() {
+        return getCollection().getEntrySet();
+    }
+
+    /**
+     * Return a {@link java.util.Set} for iterating over and interacting with all keys
+     * in the array map.
+     *
+     * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
+     * requires generating a number of temporary objects and allocates additional state
+     * information associated with the container that will remain for the life of the container.</p>
+     */
+    @Override
+    public Set<K> keySet() {
+        return getCollection().getKeySet();
+    }
+
+    /**
+     * Return a {@link java.util.Collection} for iterating over and interacting with all values
+     * in the array map.
+     *
+     * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
+     * requires generating a number of temporary objects and allocates additional state
+     * information associated with the container that will remain for the life of the container.</p>
+     */
+    @Override
+    public Collection<V> values() {
+        return getCollection().getValues();
+    }
+}
diff --git a/android/util/ArraySet.java b/android/util/ArraySet.java
new file mode 100644
index 0000000..7f652ba
--- /dev/null
+++ b/android/util/ArraySet.java
@@ -0,0 +1,982 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import libcore.util.EmptyArray;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * ArraySet is a generic set data structure that is designed to be more memory efficient than a
+ * traditional {@link java.util.HashSet}.  The design is very similar to
+ * {@link ArrayMap}, with all of the caveats described there.  This implementation is
+ * separate from ArrayMap, however, so the Object array contains only one item for each
+ * entry in the set (instead of a pair for a mapping).
+ *
+ * <p>Note that this implementation is not intended to be appropriate for data structures
+ * that may contain large numbers of items.  It is generally slower than a traditional
+ * HashSet, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array.  For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>Because this container is intended to better balance memory use, unlike most other
+ * standard Java containers it will shrink its array as items are removed from it.  Currently
+ * 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>
+ *
+ * <p>This structure is <b>NOT</b> thread-safe.</p>
+ */
+public final class ArraySet<E> implements Collection<E>, Set<E> {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "ArraySet";
+
+    /**
+     * The minimum amount by which the capacity of a ArraySet will increase.
+     * This is tuned to be relatively space-efficient.
+     */
+    private static final int BASE_SIZE = 4;
+
+    /**
+     * Maximum number of entries to have in array caches.
+     */
+    private static final int CACHE_SIZE = 10;
+
+    /**
+     * Caches of small array objects to avoid spamming garbage.  The cache
+     * Object[] variable is a pointer to a linked list of array objects.
+     * The first entry in the array is a pointer to the next array in the
+     * list; the second entry is a pointer to the int[] hash code array for it.
+     */
+    static Object[] sBaseCache;
+    static int sBaseCacheSize;
+    static Object[] sTwiceBaseCache;
+    static int sTwiceBaseCacheSize;
+    /**
+     * Separate locks for each cache since each can be accessed independently of the other without
+     * risk of a deadlock.
+     */
+    private static final Object sBaseCacheLock = new Object();
+    private static final Object sTwiceBaseCacheLock = new Object();
+
+    private final boolean mIdentityHashCode;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public API.
+    int[] mHashes;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public API.
+    Object[] mArray;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
+    int mSize;
+    private MapCollections<E, E> mCollections;
+
+    private int binarySearch(int[] hashes, int hash) {
+        try {
+            return ContainerHelpers.binarySearch(hashes, mSize, hash);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new ConcurrentModificationException();
+        }
+    }
+
+
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
+    private int indexOf(Object key, int hash) {
+        final int N = mSize;
+
+        // Important fast case: if nothing is in here, nothing to look for.
+        if (N == 0) {
+            return ~0;
+        }
+
+        int index = binarySearch(mHashes, hash);
+
+        // If the hash code wasn't found, then we have no entry for this key.
+        if (index < 0) {
+            return index;
+        }
+
+        // If the key at the returned index matches, that's what we want.
+        if (key.equals(mArray[index])) {
+            return index;
+        }
+
+        // Search for a matching key after the index.
+        int end;
+        for (end = index + 1; end < N && mHashes[end] == hash; end++) {
+            if (key.equals(mArray[end])) return end;
+        }
+
+        // Search for a matching key before the index.
+        for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
+            if (key.equals(mArray[i])) return i;
+        }
+
+        // Key not found -- return negative value indicating where a
+        // new entry for this key should go.  We use the end of the
+        // hash chain to reduce the number of array entries that will
+        // need to be copied when inserting.
+        return ~end;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null)
+    private int indexOfNull() {
+        final int N = mSize;
+
+        // Important fast case: if nothing is in here, nothing to look for.
+        if (N == 0) {
+            return ~0;
+        }
+
+        int index = binarySearch(mHashes, 0);
+
+        // If the hash code wasn't found, then we have no entry for this key.
+        if (index < 0) {
+            return index;
+        }
+
+        // If the key at the returned index matches, that's what we want.
+        if (null == mArray[index]) {
+            return index;
+        }
+
+        // Search for a matching key after the index.
+        int end;
+        for (end = index + 1; end < N && mHashes[end] == 0; end++) {
+            if (null == mArray[end]) return end;
+        }
+
+        // Search for a matching key before the index.
+        for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
+            if (null == mArray[i]) return i;
+        }
+
+        // Key not found -- return negative value indicating where a
+        // new entry for this key should go.  We use the end of the
+        // hash chain to reduce the number of array entries that will
+        // need to be copied when inserting.
+        return ~end;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    private void allocArrays(final int size) {
+        if (size == (BASE_SIZE * 2)) {
+            synchronized (sTwiceBaseCacheLock) {
+                if (sTwiceBaseCache != null) {
+                    final Object[] array = sTwiceBaseCache;
+                    try {
+                        mArray = array;
+                        sTwiceBaseCache = (Object[]) array[0];
+                        mHashes = (int[]) array[1];
+                        if (mHashes != null) {
+                            array[0] = array[1] = null;
+                            sTwiceBaseCacheSize--;
+                            if (DEBUG) {
+                                Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
+                                        + sTwiceBaseCacheSize + " entries");
+                            }
+                            return;
+                        }
+                    } catch (ClassCastException e) {
+                    }
+                    // Whoops!  Someone trampled the array (probably due to not protecting
+                    // their access with a lock).  Our cache is corrupt; report and give up.
+                    Slog.wtf(TAG, "Found corrupt ArraySet cache: [0]=" + array[0]
+                            + " [1]=" + array[1]);
+                    sTwiceBaseCache = null;
+                    sTwiceBaseCacheSize = 0;
+                }
+            }
+        } else if (size == BASE_SIZE) {
+            synchronized (sBaseCacheLock) {
+                if (sBaseCache != null) {
+                    final Object[] array = sBaseCache;
+                    try {
+                        mArray = array;
+                        sBaseCache = (Object[]) array[0];
+                        mHashes = (int[]) array[1];
+                        if (mHashes != null) {
+                            array[0] = array[1] = null;
+                            sBaseCacheSize--;
+                            if (DEBUG) {
+                                Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have "
+                                        + sBaseCacheSize + " entries");
+                            }
+                            return;
+                        }
+                    } catch (ClassCastException e) {
+                    }
+                    // Whoops!  Someone trampled the array (probably due to not protecting
+                    // their access with a lock).  Our cache is corrupt; report and give up.
+                    Slog.wtf(TAG, "Found corrupt ArraySet cache: [0]=" + array[0]
+                            + " [1]=" + array[1]);
+                    sBaseCache = null;
+                    sBaseCacheSize = 0;
+                }
+            }
+        }
+
+        mHashes = new int[size];
+        mArray = new Object[size];
+    }
+
+    /**
+     * Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other
+     * words, don't pass mHashes or mArray in directly.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
+    private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
+        if (hashes.length == (BASE_SIZE * 2)) {
+            synchronized (sTwiceBaseCacheLock) {
+                if (sTwiceBaseCacheSize < CACHE_SIZE) {
+                    array[0] = sTwiceBaseCache;
+                    array[1] = hashes;
+                    for (int i = size - 1; i >= 2; i--) {
+                        array[i] = null;
+                    }
+                    sTwiceBaseCache = array;
+                    sTwiceBaseCacheSize++;
+                    if (DEBUG) {
+                        Log.d(TAG, "Storing 2x cache " + array + " now have " + sTwiceBaseCacheSize
+                                + " entries");
+                    }
+                }
+            }
+        } else if (hashes.length == BASE_SIZE) {
+            synchronized (sBaseCacheLock) {
+                if (sBaseCacheSize < CACHE_SIZE) {
+                    array[0] = sBaseCache;
+                    array[1] = hashes;
+                    for (int i = size - 1; i >= 2; i--) {
+                        array[i] = null;
+                    }
+                    sBaseCache = array;
+                    sBaseCacheSize++;
+                    if (DEBUG) {
+                        Log.d(TAG, "Storing 1x cache " + array + " now have "
+                                + sBaseCacheSize + " entries");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a new empty ArraySet.  The default capacity of an array map is 0, and
+     * will grow once items are added to it.
+     */
+    public ArraySet() {
+        this(0, false);
+    }
+
+    /**
+     * Create a new ArraySet with a given initial capacity.
+     */
+    public ArraySet(int capacity) {
+        this(capacity, false);
+    }
+
+    /** {@hide} */
+    public ArraySet(int capacity, boolean identityHashCode) {
+        mIdentityHashCode = identityHashCode;
+        if (capacity == 0) {
+            mHashes = EmptyArray.INT;
+            mArray = EmptyArray.OBJECT;
+        } else {
+            allocArrays(capacity);
+        }
+        mSize = 0;
+    }
+
+    /**
+     * Create a new ArraySet with the mappings from the given ArraySet.
+     */
+    public ArraySet(ArraySet<E> set) {
+        this();
+        if (set != null) {
+            addAll(set);
+        }
+    }
+
+    /**
+     * Create a new ArraySet with items from the given collection.
+     */
+    public ArraySet(Collection<? extends E> set) {
+        this();
+        if (set != null) {
+            addAll(set);
+        }
+    }
+
+    /**
+     * Create a new ArraySet with items from the given array
+     */
+    public ArraySet(@Nullable E[] array) {
+        this();
+        if (array != null) {
+            for (E value : array) {
+                add(value);
+            }
+        }
+    }
+
+    /**
+     * Make the array map empty.  All storage is released.
+     */
+    @Override
+    public void clear() {
+        if (mSize != 0) {
+            final int[] ohashes = mHashes;
+            final Object[] oarray = mArray;
+            final int osize = mSize;
+            mHashes = EmptyArray.INT;
+            mArray = EmptyArray.OBJECT;
+            mSize = 0;
+            freeArrays(ohashes, oarray, osize);
+        }
+        if (mSize != 0) {
+            throw new ConcurrentModificationException();
+        }
+    }
+
+    /**
+     * Ensure the array map can hold at least <var>minimumCapacity</var>
+     * items.
+     */
+    public void ensureCapacity(int minimumCapacity) {
+        final int oSize = mSize;
+        if (mHashes.length < minimumCapacity) {
+            final int[] ohashes = mHashes;
+            final Object[] oarray = mArray;
+            allocArrays(minimumCapacity);
+            if (mSize > 0) {
+                System.arraycopy(ohashes, 0, mHashes, 0, mSize);
+                System.arraycopy(oarray, 0, mArray, 0, mSize);
+            }
+            freeArrays(ohashes, oarray, mSize);
+        }
+        if (mSize != oSize) {
+            throw new ConcurrentModificationException();
+        }
+    }
+
+    /**
+     * Check whether a value exists in the set.
+     *
+     * @param key The value to search for.
+     * @return Returns true if the value exists, else false.
+     */
+    @Override
+    public boolean contains(Object key) {
+        return indexOf(key) >= 0;
+    }
+
+    /**
+     * Returns the index of a value in the set.
+     *
+     * @param key The value to search for.
+     * @return Returns the index of the value if it exists, else a negative integer.
+     */
+    public int indexOf(Object key) {
+        return key == null ? indexOfNull()
+                : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
+    }
+
+    /**
+     * Return the value at the given index in the array.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     *
+     * @param index The desired index, must be between 0 and {@link #size()}-1.
+     * @return Returns the value stored at the given index.
+     */
+    public E valueAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return valueAtUnchecked(index);
+    }
+
+    /**
+     * Returns the value at the given index in the array without checking that the index is within
+     * bounds. This allows testing values at the end of the internal array, outside of the
+     * [0, mSize) bounds.
+     *
+     * @hide
+     */
+    @TestApi
+    public E valueAtUnchecked(int index) {
+        return (E) mArray[index];
+    }
+
+    /**
+     * Return true if the array map contains no items.
+     */
+    @Override
+    public boolean isEmpty() {
+        return mSize <= 0;
+    }
+
+    /**
+     * Adds the specified object to this set. The set is not modified if it
+     * already contains the object.
+     *
+     * @param value the object to add.
+     * @return {@code true} if this set is modified, {@code false} otherwise.
+     */
+    @Override
+    public boolean add(E value) {
+        final int oSize = mSize;
+        final int hash;
+        int index;
+        if (value == null) {
+            hash = 0;
+            index = indexOfNull();
+        } else {
+            hash = mIdentityHashCode ? System.identityHashCode(value) : value.hashCode();
+            index = indexOf(value, hash);
+        }
+        if (index >= 0) {
+            return false;
+        }
+
+        index = ~index;
+        if (oSize >= mHashes.length) {
+            final int n = oSize >= (BASE_SIZE * 2) ? (oSize + (oSize >> 1))
+                    : (oSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
+
+            if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n);
+
+            final int[] ohashes = mHashes;
+            final Object[] oarray = mArray;
+            allocArrays(n);
+
+            if (oSize != mSize) {
+                throw new ConcurrentModificationException();
+            }
+
+            if (mHashes.length > 0) {
+                if (DEBUG) Log.d(TAG, "add: copy 0-" + oSize + " to 0");
+                System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
+                System.arraycopy(oarray, 0, mArray, 0, oarray.length);
+            }
+
+            freeArrays(ohashes, oarray, oSize);
+        }
+
+        if (index < oSize) {
+            if (DEBUG) {
+                Log.d(TAG, "add: move " + index + "-" + (oSize - index) + " to " + (index + 1));
+            }
+            System.arraycopy(mHashes, index, mHashes, index + 1, oSize - index);
+            System.arraycopy(mArray, index, mArray, index + 1, oSize - index);
+        }
+
+        if (oSize != mSize || index >= mHashes.length) {
+            throw new ConcurrentModificationException();
+        }
+
+        mHashes[index] = hash;
+        mArray[index] = value;
+        mSize++;
+        return true;
+    }
+
+    /**
+     * Special fast path for appending items to the end of the array without validation.
+     * The array must already be large enough to contain the item.
+     * @hide
+     */
+    public void append(E value) {
+        final int oSize = mSize;
+        final int index = mSize;
+        final int hash = value == null ? 0
+                : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
+        if (index >= mHashes.length) {
+            throw new IllegalStateException("Array is full");
+        }
+        if (index > 0 && mHashes[index - 1] > hash) {
+            // Cannot optimize since it would break the sorted order - fallback to add()
+            if (DEBUG) {
+                RuntimeException e = new RuntimeException("here");
+                e.fillInStackTrace();
+                Log.w(TAG, "New hash " + hash
+                        + " is before end of array hash " + mHashes[index - 1]
+                        + " at index " + index, e);
+            }
+            add(value);
+            return;
+        }
+
+        if (oSize != mSize) {
+            throw new ConcurrentModificationException();
+        }
+
+        mSize = index + 1;
+        mHashes[index] = hash;
+        mArray[index] = value;
+    }
+
+    /**
+     * Perform a {@link #add(Object)} of all values in <var>array</var>
+     * @param array The array whose contents are to be retrieved.
+     */
+    public void addAll(ArraySet<? extends E> array) {
+        final int N = array.mSize;
+        ensureCapacity(mSize + N);
+        if (mSize == 0) {
+            if (N > 0) {
+                System.arraycopy(array.mHashes, 0, mHashes, 0, N);
+                System.arraycopy(array.mArray, 0, mArray, 0, N);
+                if (0 != mSize) {
+                    throw new ConcurrentModificationException();
+                }
+                mSize = N;
+            }
+        } else {
+            for (int i = 0; i < N; i++) {
+                add(array.valueAt(i));
+            }
+        }
+    }
+
+    /**
+     * Removes the specified object from this set.
+     *
+     * @param object the object to remove.
+     * @return {@code true} if this set was modified, {@code false} otherwise.
+     */
+    @Override
+    public boolean remove(Object object) {
+        final int index = indexOf(object);
+        if (index >= 0) {
+            removeAt(index);
+            return true;
+        }
+        return false;
+    }
+
+    /** Returns true if the array size should be decreased. */
+    private boolean shouldShrink() {
+        return mHashes.length > (BASE_SIZE * 2) && mSize < mHashes.length / 3;
+    }
+
+    /**
+     * Returns the new size the array should have. Is only valid if {@link #shouldShrink} returns
+     * true.
+     */
+    private int getNewShrunkenSize() {
+        // We don't allow it to shrink smaller than (BASE_SIZE*2) to avoid flapping between that
+        // and BASE_SIZE.
+        return mSize > (BASE_SIZE * 2) ? (mSize + (mSize >> 1)) : (BASE_SIZE * 2);
+    }
+
+    /**
+     * Remove the key/value mapping at the given index.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     *
+     * @param index The desired index, must be between 0 and {@link #size()}-1.
+     * @return Returns the value that was stored at this index.
+     */
+    public E removeAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        final int oSize = mSize;
+        final Object old = mArray[index];
+        if (oSize <= 1) {
+            // Now empty.
+            if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
+            clear();
+        } else {
+            final int nSize = oSize - 1;
+            if (shouldShrink()) {
+                // Shrunk enough to reduce size of arrays.
+                final int n = getNewShrunkenSize();
+
+                if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
+
+                final int[] ohashes = mHashes;
+                final Object[] oarray = mArray;
+                allocArrays(n);
+
+                if (index > 0) {
+                    if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
+                    System.arraycopy(ohashes, 0, mHashes, 0, index);
+                    System.arraycopy(oarray, 0, mArray, 0, index);
+                }
+                if (index < nSize) {
+                    if (DEBUG) {
+                        Log.d(TAG, "remove: copy from " + (index + 1) + "-" + nSize
+                                + " to " + index);
+                    }
+                    System.arraycopy(ohashes, index + 1, mHashes, index, nSize - index);
+                    System.arraycopy(oarray, index + 1, mArray, index, nSize - index);
+                }
+            } else {
+                if (index < nSize) {
+                    if (DEBUG) {
+                        Log.d(TAG, "remove: move " + (index + 1) + "-" + nSize + " to " + index);
+                    }
+                    System.arraycopy(mHashes, index + 1, mHashes, index, nSize - index);
+                    System.arraycopy(mArray, index + 1, mArray, index, nSize - index);
+                }
+                mArray[nSize] = null;
+            }
+            if (oSize != mSize) {
+                throw new ConcurrentModificationException();
+            }
+            mSize = nSize;
+        }
+        return (E) old;
+    }
+
+    /**
+     * Perform a {@link #remove(Object)} of all values in <var>array</var>
+     * @param array The array whose contents are to be removed.
+     */
+    public boolean removeAll(ArraySet<? extends E> array) {
+        // TODO: If array is sufficiently large, a marking approach might be beneficial. In a first
+        //       pass, use the property that the sets are sorted by hash to make this linear passes
+        //       (except for hash collisions, which means worst case still n*m), then do one
+        //       collection pass into a new array. This avoids binary searches and excessive memcpy.
+        final int N = array.mSize;
+
+        // Note: ArraySet does not make thread-safety guarantees. So instead of OR-ing together all
+        //       the single results, compare size before and after.
+        final int originalSize = mSize;
+        for (int i = 0; i < N; i++) {
+            remove(array.valueAt(i));
+        }
+        return originalSize != mSize;
+    }
+
+    /**
+     * Removes all values that satisfy the predicate. This implementation avoids using the
+     * {@link #iterator()}.
+     *
+     * @param filter A predicate which returns true for elements to be removed
+     */
+    @Override
+    public boolean removeIf(Predicate<? super E> filter) {
+        if (mSize == 0) {
+            return false;
+        }
+
+        // Intentionally not using removeAt() to avoid unnecessary intermediate resizing.
+
+        int replaceIndex = 0;
+        int numRemoved = 0;
+        for (int i = 0; i < mSize; ++i) {
+            if (filter.test((E) mArray[i])) {
+                numRemoved++;
+            } else {
+                if (replaceIndex != i) {
+                    mArray[replaceIndex] = mArray[i];
+                    mHashes[replaceIndex] = mHashes[i];
+                }
+                replaceIndex++;
+            }
+        }
+
+        if (numRemoved == 0) {
+            return false;
+        } else if (numRemoved == mSize) {
+            clear();
+            return true;
+        }
+
+        mSize -= numRemoved;
+        if (shouldShrink()) {
+            // Shrunk enough to reduce size of arrays.
+            final int n = getNewShrunkenSize();
+            final int[] ohashes = mHashes;
+            final Object[] oarray = mArray;
+            allocArrays(n);
+
+            System.arraycopy(ohashes, 0, mHashes, 0, mSize);
+            System.arraycopy(oarray, 0, mArray, 0, mSize);
+        } else {
+            // Null out values at the end of the array. Not doing it in the loop above to avoid
+            // writing twice to the same index or writing unnecessarily if the array would have been
+            // discarded anyway.
+            for (int i = mSize; i < mArray.length; ++i) {
+                mArray[i] = null;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return the number of items in this array map.
+     */
+    @Override
+    public int size() {
+        return mSize;
+    }
+
+    @Override
+    public Object[] toArray() {
+        Object[] result = new Object[mSize];
+        System.arraycopy(mArray, 0, result, 0, mSize);
+        return result;
+    }
+
+    @Override
+    public <T> T[] toArray(T[] array) {
+        if (array.length < mSize) {
+            @SuppressWarnings("unchecked") T[] newArray =
+                    (T[]) Array.newInstance(array.getClass().getComponentType(), mSize);
+            array = newArray;
+        }
+        System.arraycopy(mArray, 0, array, 0, mSize);
+        if (array.length > mSize) {
+            array[mSize] = null;
+        }
+        return array;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation returns false if the object is not a set, or
+     * if the sets have different sizes.  Otherwise, for each value in this
+     * set, it checks to make sure the value also exists in the other set.
+     * If any value doesn't exist, the method returns false; otherwise, it
+     * returns true.
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object instanceof Set) {
+            Set<?> set = (Set<?>) object;
+            if (size() != set.size()) {
+                return false;
+            }
+
+            try {
+                for (int i = 0; i < mSize; i++) {
+                    E mine = valueAt(i);
+                    if (!set.contains(mine)) {
+                        return false;
+                    }
+                }
+            } catch (NullPointerException ignored) {
+                return false;
+            } catch (ClassCastException ignored) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        final int[] hashes = mHashes;
+        int result = 0;
+        for (int i = 0, s = mSize; i < s; i++) {
+            result += hashes[i];
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its values. If
+     * this set contains itself as a value, the string "(this Set)"
+     * will appear in its place.
+     */
+    @Override
+    public String toString() {
+        if (isEmpty()) {
+            return "{}";
+        }
+
+        StringBuilder buffer = new StringBuilder(mSize * 14);
+        buffer.append('{');
+        for (int i = 0; i < mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            Object value = valueAt(i);
+            if (value != this) {
+                buffer.append(value);
+            } else {
+                buffer.append("(this Set)");
+            }
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+
+    // ------------------------------------------------------------------------
+    // Interop with traditional Java containers.  Not as efficient as using
+    // specialized collection APIs.
+    // ------------------------------------------------------------------------
+
+    private MapCollections<E, E> getCollection() {
+        if (mCollections == null) {
+            mCollections = new MapCollections<E, E>() {
+                @Override
+                protected int colGetSize() {
+                    return mSize;
+                }
+
+                @Override
+                protected Object colGetEntry(int index, int offset) {
+                    return mArray[index];
+                }
+
+                @Override
+                protected int colIndexOfKey(Object key) {
+                    return indexOf(key);
+                }
+
+                @Override
+                protected int colIndexOfValue(Object value) {
+                    return indexOf(value);
+                }
+
+                @Override
+                protected Map<E, E> colGetMap() {
+                    throw new UnsupportedOperationException("not a map");
+                }
+
+                @Override
+                protected void colPut(E key, E value) {
+                    add(key);
+                }
+
+                @Override
+                protected E colSetValue(int index, E value) {
+                    throw new UnsupportedOperationException("not a map");
+                }
+
+                @Override
+                protected void colRemoveAt(int index) {
+                    removeAt(index);
+                }
+
+                @Override
+                protected void colClear() {
+                    clear();
+                }
+            };
+        }
+        return mCollections;
+    }
+
+    /**
+     * Return an {@link java.util.Iterator} over all values in the set.
+     *
+     * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
+     * requires generating a number of temporary objects and allocates additional state
+     * information associated with the container that will remain for the life of the container.</p>
+     */
+    @Override
+    public Iterator<E> iterator() {
+        return getCollection().getKeySet().iterator();
+    }
+
+    /**
+     * Determine if the array set contains all of the values in the given collection.
+     * @param collection The collection whose contents are to be checked against.
+     * @return Returns true if this array set contains a value for every entry
+     * in <var>collection</var>, else returns false.
+     */
+    @Override
+    public boolean containsAll(Collection<?> collection) {
+        Iterator<?> it = collection.iterator();
+        while (it.hasNext()) {
+            if (!contains(it.next())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Perform an {@link #add(Object)} of all values in <var>collection</var>
+     * @param collection The collection whose contents are to be retrieved.
+     */
+    @Override
+    public boolean addAll(Collection<? extends E> collection) {
+        ensureCapacity(mSize + collection.size());
+        boolean added = false;
+        for (E value : collection) {
+            added |= add(value);
+        }
+        return added;
+    }
+
+    /**
+     * Remove all values in the array set that exist in the given collection.
+     * @param collection The collection whose contents are to be used to remove values.
+     * @return Returns true if any values were removed from the array set, else false.
+     */
+    @Override
+    public boolean removeAll(Collection<?> collection) {
+        boolean removed = false;
+        for (Object value : collection) {
+            removed |= remove(value);
+        }
+        return removed;
+    }
+
+    /**
+     * Remove all values in the array set that do <b>not</b> exist in the given collection.
+     * @param collection The collection whose contents are to be used to determine which
+     * values to keep.
+     * @return Returns true if any values were removed from the array set, else false.
+     */
+    @Override
+    public boolean retainAll(Collection<?> collection) {
+        boolean removed = false;
+        for (int i = mSize - 1; i >= 0; i--) {
+            if (!collection.contains(mArray[i])) {
+                removeAt(i);
+                removed = true;
+            }
+        }
+        return removed;
+    }
+}
diff --git a/android/util/ArraySetPerfTest.java b/android/util/ArraySetPerfTest.java
new file mode 100644
index 0000000..b24bf42
--- /dev/null
+++ b/android/util/ArraySetPerfTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Predicate;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ArraySetPerfTest {
+    private static final int NUM_ITERATIONS = 100;
+    private static final int SET_SIZE_SMALL = 10;
+    private static final int SET_SIZE_LARGE = 50;
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testValueAt_InBounds() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        ArraySet<Integer> set = new ArraySet<>();
+        set.add(0);
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                set.valueAt(0);
+            }
+        }
+    }
+
+    @Test
+    public void testValueAt_OutOfBounds_Negative() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        ArraySet<Integer> set = new ArraySet<>();
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                try {
+                    set.valueAt(-1);
+                } catch (ArrayIndexOutOfBoundsException expected) {
+                    // expected
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests the case where ArraySet could index into its array even though the index is out of
+     * bounds.
+     */
+    @Test
+    public void testValueAt_OutOfBounds_EdgeCase() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        ArraySet<Integer> set = new ArraySet<>();
+        set.add(0);
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                try {
+                    set.valueAt(1);
+                } catch (ArrayIndexOutOfBoundsException expected) {
+                    // expected
+                }
+            }
+        }
+    }
+
+    /**
+     * This is the same code as testRemoveIf_Small_* without the removeIf in order to measure
+     * the performance of the rest of the code in the loop.
+     */
+    @Test
+    public void testRemoveIf_Small_Base() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Predicate<Integer> predicate = (i) -> i % 2 == 0;
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                ArraySet<Integer> set = new ArraySet<>();
+                for (int j = 0; j < SET_SIZE_SMALL; ++j) {
+                    set.add(j);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveIf_Small_RemoveNothing() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Predicate<Integer> predicate = (i) -> false;
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                ArraySet<Integer> set = new ArraySet<>();
+                for (int j = 0; j < SET_SIZE_SMALL; ++j) {
+                    set.add(j);
+                }
+                set.removeIf(predicate);
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveIf_Small_RemoveAll() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Predicate<Integer> predicate = (i) -> true;
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                ArraySet<Integer> set = new ArraySet<>();
+                for (int j = 0; j < SET_SIZE_SMALL; j++) {
+                    set.add(j);
+                }
+                set.removeIf(predicate);
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveIf_Small_RemoveHalf() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Predicate<Integer> predicate = (i) -> i % 2 == 0;
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                ArraySet<Integer> set = new ArraySet<>();
+                for (int j = 0; j < SET_SIZE_SMALL; ++j) {
+                    set.add(j);
+                }
+                set.removeIf(predicate);
+            }
+        }
+    }
+
+    /**
+     * This is the same code as testRemoveIf_Large_* without the removeIf in order to measure
+     * the performance of the rest of the code in the loop.
+     */
+    @Test
+    public void testRemoveIf_Large_Base() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Predicate<Integer> predicate = (i) -> i % 2 == 0;
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                ArraySet<Integer> set = new ArraySet<>();
+                for (int j = 0; j < SET_SIZE_LARGE; ++j) {
+                    set.add(j);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveIf_Large_RemoveNothing() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Predicate<Integer> predicate = (i) -> false;
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                ArraySet<Integer> set = new ArraySet<>();
+                for (int j = 0; j < SET_SIZE_LARGE; ++j) {
+                    set.add(j);
+                }
+                set.removeIf(predicate);
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveIf_Large_RemoveAll() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Predicate<Integer> predicate = (i) -> true;
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                ArraySet<Integer> set = new ArraySet<>();
+                for (int j = 0; j < SET_SIZE_LARGE; ++j) {
+                    set.add(j);
+                }
+                set.removeIf(predicate);
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveIf_Large_RemoveHalf() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Predicate<Integer> predicate = (i) -> i % 2 == 0;
+        while (state.keepRunning()) {
+            for (int i = 0; i < NUM_ITERATIONS; ++i) {
+                ArraySet<Integer> set = new ArraySet<>();
+                for (int j = 0; j < SET_SIZE_LARGE; ++j) {
+                    set.add(j);
+                }
+                set.removeIf(predicate);
+            }
+        }
+    }
+}
diff --git a/android/util/AtomicFile.java b/android/util/AtomicFile.java
new file mode 100644
index 0000000..cf7ed9b
--- /dev/null
+++ b/android/util/AtomicFile.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2009 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;
+
+import android.os.FileUtils;
+import android.os.SystemClock;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.function.Consumer;
+
+/**
+ * Helper class for performing atomic operations on a file by creating a
+ * backup file until a write has successfully completed.  If you need this
+ * on older versions of the platform you can use
+ * {@link android.support.v4.util.AtomicFile} in the v4 support library.
+ * <p>
+ * Atomic file guarantees file integrity by ensuring that a file has
+ * been completely written and sync'd to disk before removing its backup.
+ * As long as the backup file exists, the original file is considered
+ * to be invalid (left over from a previous attempt to write the file).
+ * </p><p>
+ * Atomic file does not confer any file locking semantics.
+ * Do not use this class when the file may be accessed or modified concurrently
+ * by multiple threads or processes.  The caller is responsible for ensuring
+ * appropriate mutual exclusion invariants whenever it accesses the file.
+ * </p>
+ */
+public class AtomicFile {
+    private final File mBaseName;
+    private final File mBackupName;
+    private final String mCommitTag;
+    private long mStartTime;
+
+    /**
+     * Create a new AtomicFile for a file located at the given File path.
+     * The secondary backup file will be the same file path with ".bak" appended.
+     */
+    public AtomicFile(File baseName) {
+        this(baseName, null);
+    }
+
+    /**
+     * @hide Internal constructor that also allows you to have the class
+     * automatically log commit events.
+     */
+    public AtomicFile(File baseName, String commitTag) {
+        mBaseName = baseName;
+        mBackupName = new File(baseName.getPath() + ".bak");
+        mCommitTag = commitTag;
+    }
+
+    /**
+     * Return the path to the base file.  You should not generally use this,
+     * as the data at that path may not be valid.
+     */
+    public File getBaseFile() {
+        return mBaseName;
+    }
+
+    /**
+     * Delete the atomic file.  This deletes both the base and backup files.
+     */
+    public void delete() {
+        mBaseName.delete();
+        mBackupName.delete();
+    }
+
+    /**
+     * Start a new write operation on the file.  This returns a FileOutputStream
+     * to which you can write the new file data.  The existing file is replaced
+     * with the new data.  You <em>must not</em> directly close the given
+     * FileOutputStream; instead call either {@link #finishWrite(FileOutputStream)}
+     * or {@link #failWrite(FileOutputStream)}.
+     *
+     * <p>Note that if another thread is currently performing
+     * a write, this will simply replace whatever that thread is writing
+     * with the new file being written by this thread, and when the other
+     * thread finishes the write the new write operation will no longer be
+     * safe (or will be lost).  You must do your own threading protection for
+     * access to AtomicFile.
+     */
+    public FileOutputStream startWrite() throws IOException {
+        return startWrite(mCommitTag != null ? SystemClock.uptimeMillis() : 0);
+    }
+
+    /**
+     * @hide Internal version of {@link #startWrite()} that allows you to specify an earlier
+     * start time of the operation to adjust how the commit is logged.
+     * @param startTime The effective start time of the operation, in the time
+     * base of {@link SystemClock#uptimeMillis()}.
+     */
+    public FileOutputStream startWrite(long startTime) throws IOException {
+        mStartTime = startTime;
+
+        // Rename the current file so it may be used as a backup during the next read
+        if (mBaseName.exists()) {
+            if (!mBackupName.exists()) {
+                if (!mBaseName.renameTo(mBackupName)) {
+                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
+                            + " to backup file " + mBackupName);
+                }
+            } else {
+                mBaseName.delete();
+            }
+        }
+        FileOutputStream str = null;
+        try {
+            str = new FileOutputStream(mBaseName);
+        } catch (FileNotFoundException e) {
+            File parent = mBaseName.getParentFile();
+            if (!parent.mkdirs()) {
+                throw new IOException("Couldn't create directory " + mBaseName);
+            }
+            FileUtils.setPermissions(
+                parent.getPath(),
+                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                -1, -1);
+            try {
+                str = new FileOutputStream(mBaseName);
+            } catch (FileNotFoundException e2) {
+                throw new IOException("Couldn't create " + mBaseName);
+            }
+        }
+        return str;
+    }
+
+    /**
+     * Call when you have successfully finished writing to the stream
+     * returned by {@link #startWrite()}.  This will close, sync, and
+     * commit the new data.  The next attempt to read the atomic file
+     * will return the new file stream.
+     */
+    public void finishWrite(FileOutputStream str) {
+        if (str != null) {
+            FileUtils.sync(str);
+            try {
+                str.close();
+                mBackupName.delete();
+            } catch (IOException e) {
+                Log.w("AtomicFile", "finishWrite: Got exception:", e);
+            }
+            if (mCommitTag != null) {
+                com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+                        mCommitTag, SystemClock.uptimeMillis() - mStartTime);
+            }
+        }
+    }
+
+    /**
+     * Call when you have failed for some reason at writing to the stream
+     * returned by {@link #startWrite()}.  This will close the current
+     * write stream, and roll back to the previous state of the file.
+     */
+    public void failWrite(FileOutputStream str) {
+        if (str != null) {
+            FileUtils.sync(str);
+            try {
+                str.close();
+                mBaseName.delete();
+                mBackupName.renameTo(mBaseName);
+            } catch (IOException e) {
+                Log.w("AtomicFile", "failWrite: Got exception:", e);
+            }
+        }
+    }
+
+    /** @hide
+     * @deprecated This is not safe.
+     */
+    @Deprecated public void truncate() throws IOException {
+        try {
+            FileOutputStream fos = new FileOutputStream(mBaseName);
+            FileUtils.sync(fos);
+            fos.close();
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        } catch (IOException e) {
+        }
+    }
+
+    /** @hide
+     * @deprecated This is not safe.
+     */
+    @Deprecated public FileOutputStream openAppend() throws IOException {
+        try {
+            return new FileOutputStream(mBaseName, true);
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        }
+    }
+
+    /**
+     * Open the atomic file for reading.  If there previously was an
+     * incomplete write, this will roll back to the last good data before
+     * opening for read.  You should call close() on the FileInputStream when
+     * you are done reading from it.
+     *
+     * <p>Note that if another thread is currently performing
+     * a write, this will incorrectly consider it to be in the state of a bad
+     * write and roll back, causing the new data currently being written to
+     * be dropped.  You must do your own threading protection for access to
+     * AtomicFile.
+     */
+    public FileInputStream openRead() throws FileNotFoundException {
+        if (mBackupName.exists()) {
+            mBaseName.delete();
+            mBackupName.renameTo(mBaseName);
+        }
+        return new FileInputStream(mBaseName);
+    }
+
+    /**
+     * @hide
+     * Checks if the original or backup file exists.
+     * @return whether the original or backup file exists.
+     */
+    public boolean exists() {
+        return mBaseName.exists() || mBackupName.exists();
+    }
+
+    /**
+     * Gets the last modified time of the atomic file.
+     * {@hide}
+     *
+     * @return last modified time in milliseconds since epoch.  Returns zero if
+     *     the file does not exist or an I/O error is encountered.
+     */
+    public long getLastModifiedTime() {
+        if (mBackupName.exists()) {
+            return mBackupName.lastModified();
+        }
+        return mBaseName.lastModified();
+    }
+
+    /**
+     * A convenience for {@link #openRead()} that also reads all of the
+     * file contents into a byte array which is returned.
+     */
+    public byte[] readFully() throws IOException {
+        FileInputStream stream = openRead();
+        try {
+            int pos = 0;
+            int avail = stream.available();
+            byte[] data = new byte[avail];
+            while (true) {
+                int amt = stream.read(data, pos, data.length-pos);
+                //Log.i("foo", "Read " + amt + " bytes at " + pos
+                //        + " of avail " + data.length);
+                if (amt <= 0) {
+                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                    //        + " len=" + data.length);
+                    return data;
+                }
+                pos += amt;
+                avail = stream.available();
+                if (avail > data.length-pos) {
+                    byte[] newData = new byte[pos+avail];
+                    System.arraycopy(data, 0, newData, 0, pos);
+                    data = newData;
+                }
+            }
+        } finally {
+            stream.close();
+        }
+    }
+
+    /** @hide */
+    public void write(Consumer<FileOutputStream> writeContent) {
+        FileOutputStream out = null;
+        try {
+            out = startWrite();
+            writeContent.accept(out);
+            finishWrite(out);
+        } catch (Throwable t) {
+            failWrite(out);
+            throw ExceptionUtils.propagate(t);
+        } finally {
+            IoUtils.closeQuietly(out);
+        }
+    }
+}
diff --git a/android/util/AttributeSet.java b/android/util/AttributeSet.java
new file mode 100644
index 0000000..7f327c7
--- /dev/null
+++ b/android/util/AttributeSet.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2006 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;
+
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A collection of attributes, as found associated with a tag in an XML
+ * document.  Often you will not want to use this interface directly, instead
+ * passing it to {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+ * Resources.Theme.obtainStyledAttributes()}
+ * which will take care of parsing the attributes for you.  In particular,
+ * the Resources API will convert resource references (attribute values such as
+ * "@string/my_label" in the original XML) to the desired type
+ * for you; if you use AttributeSet directly then you will need to manually
+ * check for resource references
+ * (with {@link #getAttributeResourceValue(int, int)}) and do the resource
+ * lookup yourself if needed.  Direct use of AttributeSet also prevents the
+ * application of themes and styles when retrieving attribute values.
+ * 
+ * <p>This interface provides an efficient mechanism for retrieving
+ * data from compiled XML files, which can be retrieved for a particular
+ * XmlPullParser through {@link Xml#asAttributeSet
+ * Xml.asAttributeSet()}.  Normally this will return an implementation
+ * of the interface that works on top of a generic XmlPullParser, however it
+ * is more useful in conjunction with compiled XML resources:
+ * 
+ * <pre>
+ * XmlPullParser parser = resources.getXml(myResource);
+ * AttributeSet attributes = Xml.asAttributeSet(parser);</pre>
+ * 
+ * <p>The implementation returned here, unlike using
+ * the implementation on top of a generic XmlPullParser,
+ * is highly optimized by retrieving pre-computed information that was
+ * generated by aapt when compiling your resources.  For example,
+ * the {@link #getAttributeFloatValue(int, float)} method returns a floating
+ * point number previous stored in the compiled resource instead of parsing
+ * at runtime the string originally in the XML file.
+ * 
+ * <p>This interface also provides additional information contained in the
+ * compiled XML resource that is not available in a normal XML file, such
+ * as {@link #getAttributeNameResource(int)} which returns the resource
+ * identifier associated with a particular XML attribute name.
+ *
+ * @see XmlPullParser
+ */
+public interface AttributeSet {
+    /**
+     * Returns the number of attributes available in the set.
+     *
+     * <p>See also {@link XmlPullParser#getAttributeCount XmlPullParser.getAttributeCount()},
+     * which this method corresponds to when parsing a compiled XML file.</p>
+     *
+     * @return A positive integer, or 0 if the set is empty.
+     */
+    public int getAttributeCount();
+
+    /**
+     * Returns the namespace of the specified attribute.
+     *
+     * <p>See also {@link XmlPullParser#getAttributeNamespace XmlPullParser.getAttributeNamespace()},
+     * which this method corresponds to when parsing a compiled XML file.</p>
+     *
+     * @param index Index of the desired attribute, 0...count-1.
+     *
+     * @return A String containing the namespace of the attribute, or null if th
+     *         attribute cannot be found.
+     */
+    default String getAttributeNamespace (int index) {
+        // This is a new method since the first interface definition, so add stub impl.
+        return null;
+    }
+
+    /**
+     * Returns the name of the specified attribute.
+     *
+     * <p>See also {@link XmlPullParser#getAttributeName XmlPullParser.getAttributeName()},
+     * which this method corresponds to when parsing a compiled XML file.</p>
+     *
+     * @param index Index of the desired attribute, 0...count-1.
+     * 
+     * @return A String containing the name of the attribute, or null if the
+     *         attribute cannot be found.
+     */
+    public String getAttributeName(int index);
+
+    /**
+     * Returns the value of the specified attribute as a string representation.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * 
+     * @return A String containing the value of the attribute, or null if the
+     *         attribute cannot be found.
+     */
+    public String getAttributeValue(int index);
+
+    /**
+     * Returns the value of the specified attribute as a string representation.
+     * The lookup is performed using the attribute name.
+     * 
+     * @param namespace The namespace of the attribute to get the value from.
+     * @param name The name of the attribute to get the value from.
+     * 
+     * @return A String containing the value of the attribute, or null if the
+     *         attribute cannot be found.
+     */
+    public String getAttributeValue(String namespace, String name);
+
+    /**
+     * Returns a description of the current position of the attribute set.
+     * For instance, if the attribute set is loaded from an XML document,
+     * the position description could indicate the current line number.
+     * 
+     * @return A string representation of the current position in the set,
+     *         may be null.
+     */
+    public String getPositionDescription();
+
+    /**
+     * Return the resource ID associated with the given attribute name.  This
+     * will be the identifier for an attribute resource, which can be used by
+     * styles.  Returns 0 if there is no resource associated with this
+     * attribute.
+     * 
+     * <p>Note that this is different than {@link #getAttributeResourceValue}
+     * in that it returns a resource identifier for the attribute name; the
+     * other method returns this attribute's value as a resource identifier.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * 
+     * @return The resource identifier, 0 if none.
+     */
+    public int getAttributeNameResource(int index);
+
+    /**
+     * Return the index of the value of 'attribute' in the list 'options'.
+     *
+     * @param namespace Namespace of attribute to retrieve. 
+     * @param attribute Name of attribute to retrieve.
+     * @param options List of strings whose values we are checking against.
+     * @param defaultValue Value returned if attribute doesn't exist or no
+     *                     match is found.
+     * 
+     * @return Index in to 'options' or defaultValue.
+     */
+    public int getAttributeListValue(String namespace, String attribute,
+                                     String[] options, int defaultValue);
+
+    /**
+     * Return the boolean value of 'attribute'.
+     * 
+     * @param namespace Namespace of attribute to retrieve.
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public boolean getAttributeBooleanValue(String namespace, String attribute,
+                                            boolean defaultValue);
+
+    /**
+     * Return the value of 'attribute' as a resource identifier.
+     * 
+     * <p>Note that this is different than {@link #getAttributeNameResource}
+     * in that it returns the value contained in this attribute as a
+     * resource identifier (i.e., a value originally of the form
+     * "@package:type/resource"); the other method returns a resource
+     * identifier that identifies the name of the attribute.
+     * 
+     * @param namespace Namespace of attribute to retrieve.
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeResourceValue(String namespace, String attribute,
+                                         int defaultValue);
+
+    /**
+     * Return the integer value of 'attribute'.
+     * 
+     * @param namespace Namespace of attribute to retrieve.
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeIntValue(String namespace, String attribute,
+                                    int defaultValue);
+
+    /**
+     * Return the boolean value of 'attribute' that is formatted as an
+     * unsigned value.  In particular, the formats 0xn...n and #n...n are
+     * handled.
+     * 
+     * @param namespace Namespace of attribute to retrieve.
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeUnsignedIntValue(String namespace, String attribute,
+                                            int defaultValue);
+
+    /**
+     * Return the float value of 'attribute'.
+     * 
+     * @param namespace Namespace of attribute to retrieve.
+     * @param attribute The attribute to retrieve.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public float getAttributeFloatValue(String namespace, String attribute,
+                                        float defaultValue);
+
+    /**
+     * Return the index of the value of attribute at 'index' in the list 
+     * 'options'. 
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param options List of strings whose values we are checking against.
+     * @param defaultValue Value returned if attribute doesn't exist or no
+     *                     match is found.
+     * 
+     * @return Index in to 'options' or defaultValue.
+     */
+    public int getAttributeListValue(int index, String[] options, int defaultValue);
+
+    /**
+     * Return the boolean value of attribute at 'index'.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public boolean getAttributeBooleanValue(int index, boolean defaultValue);
+
+    /**
+     * Return the value of attribute at 'index' as a resource identifier.
+     * 
+     * <p>Note that this is different than {@link #getAttributeNameResource}
+     * in that it returns the value contained in this attribute as a
+     * resource identifier (i.e., a value originally of the form
+     * "@package:type/resource"); the other method returns a resource
+     * identifier that identifies the name of the attribute.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeResourceValue(int index, int defaultValue);
+
+    /**
+     * Return the integer value of attribute at 'index'.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeIntValue(int index, int defaultValue);
+
+    /**
+     * Return the integer value of attribute at 'index' that is formatted as an
+     * unsigned value.  In particular, the formats 0xn...n and #n...n are
+     * handled.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public int getAttributeUnsignedIntValue(int index, int defaultValue);
+
+    /**
+     * Return the float value of attribute at 'index'.
+     * 
+     * @param index Index of the desired attribute, 0...count-1.
+     * @param defaultValue What to return if the attribute isn't found.
+     * 
+     * @return Resulting value.
+     */
+    public float getAttributeFloatValue(int index, float defaultValue);
+
+    /**
+     * Return the value of the "id" attribute or null if there is not one.
+     * Equivalent to getAttributeValue(null, "id").
+     * 
+     * @return The id attribute's value or null.
+     */
+    public String getIdAttribute();
+
+    /**
+     * Return the value of the "class" attribute or null if there is not one.
+     * Equivalent to getAttributeValue(null, "class").
+     * 
+     * @return The class attribute's value or null.
+     */
+    public String getClassAttribute();
+
+    /**
+     * Return the integer value of the "id" attribute or defaultValue if there
+     * is none.
+     * Equivalent to getAttributeResourceValue(null, "id", defaultValue);
+     *
+     * @param defaultValue What to return if the "id" attribute isn't found.
+     * @return int Resulting value.
+     */
+    public int getIdAttributeResourceValue(int defaultValue);
+
+    /**
+
+     * Return the value of the "style" attribute or 0 if there is not one.
+     * Equivalent to getAttributeResourceValue(null, "style").
+     * 
+     * @return The style attribute's resource identifier or 0.
+     */
+    public int getStyleAttribute();
+}
diff --git a/android/util/BackupUtils.java b/android/util/BackupUtils.java
new file mode 100644
index 0000000..474ceda
--- /dev/null
+++ b/android/util/BackupUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008 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;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Utility methods for Backup/Restore
+ * @hide
+ */
+public class BackupUtils {
+
+    public static final int NULL = 0;
+    public static final int NOT_NULL = 1;
+
+    /**
+     * Thrown when there is a backup version mismatch
+     * between the data received and what the system can handle
+     */
+    public static class BadVersionException extends Exception {
+        public BadVersionException(String message) {
+            super(message);
+        }
+    }
+
+    public static String readString(DataInputStream in) throws IOException {
+        return (in.readByte() == NOT_NULL) ? in.readUTF() : null;
+    }
+
+    public static void writeString(DataOutputStream out, String val) throws IOException {
+        if (val != null) {
+            out.writeByte(NOT_NULL);
+            out.writeUTF(val);
+        } else {
+            out.writeByte(NULL);
+        }
+    }
+}
\ No newline at end of file
diff --git a/android/util/Base64.java b/android/util/Base64.java
new file mode 100644
index 0000000..92abd7c
--- /dev/null
+++ b/android/util/Base64.java
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2010 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Utilities for encoding and decoding the Base64 representation of
+ * binary data.  See RFCs <a
+ * href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
+ * href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
+ */
+public class Base64 {
+    /**
+     * Default values for encoder/decoder flags.
+     */
+    public static final int DEFAULT = 0;
+
+    /**
+     * Encoder flag bit to omit the padding '=' characters at the end
+     * of the output (if any).
+     */
+    public static final int NO_PADDING = 1;
+
+    /**
+     * Encoder flag bit to omit all line terminators (i.e., the output
+     * will be on one long line).
+     */
+    public static final int NO_WRAP = 2;
+
+    /**
+     * Encoder flag bit to indicate lines should be terminated with a
+     * CRLF pair instead of just an LF.  Has no effect if {@code
+     * NO_WRAP} is specified as well.
+     */
+    public static final int CRLF = 4;
+
+    /**
+     * Encoder/decoder flag bit to indicate using the "URL and
+     * filename safe" variant of Base64 (see RFC 3548 section 4) where
+     * {@code -} and {@code _} are used in place of {@code +} and
+     * {@code /}.
+     */
+    public static final int URL_SAFE = 8;
+
+    /**
+     * Flag to pass to {@link Base64OutputStream} to indicate that it
+     * should not close the output stream it is wrapping when it
+     * itself is closed.
+     */
+    public static final int NO_CLOSE = 16;
+
+    //  --------------------------------------------------------
+    //  shared code
+    //  --------------------------------------------------------
+
+    /* package */ static abstract class Coder {
+        public byte[] output;
+        public int op;
+
+        /**
+         * Encode/decode another block of input data.  this.output is
+         * provided by the caller, and must be big enough to hold all
+         * the coded data.  On exit, this.opwill be set to the length
+         * of the coded data.
+         *
+         * @param finish true if this is the final call to process for
+         *        this object.  Will finalize the coder state and
+         *        include any final bytes in the output.
+         *
+         * @return true if the input so far is good; false if some
+         *         error has been detected in the input stream..
+         */
+        public abstract boolean process(byte[] input, int offset, int len, boolean finish);
+
+        /**
+         * @return the maximum number of bytes a call to process()
+         * could produce for the given number of input bytes.  This may
+         * be an overestimate.
+         */
+        public abstract int maxOutputSize(int len);
+    }
+
+    //  --------------------------------------------------------
+    //  decoding
+    //  --------------------------------------------------------
+
+    /**
+     * Decode the Base64-encoded data in input and return the data in
+     * a new byte array.
+     *
+     * <p>The padding '=' characters at the end are considered optional, but
+     * if any are present, there must be the correct number of them.
+     *
+     * @param str    the input String to decode, which is converted to
+     *               bytes using the default charset
+     * @param flags  controls certain features of the decoded output.
+     *               Pass {@code DEFAULT} to decode standard Base64.
+     *
+     * @throws IllegalArgumentException if the input contains
+     * incorrect padding
+     */
+    public static byte[] decode(String str, int flags) {
+        return decode(str.getBytes(), flags);
+    }
+
+    /**
+     * Decode the Base64-encoded data in input and return the data in
+     * a new byte array.
+     *
+     * <p>The padding '=' characters at the end are considered optional, but
+     * if any are present, there must be the correct number of them.
+     *
+     * @param input the input array to decode
+     * @param flags  controls certain features of the decoded output.
+     *               Pass {@code DEFAULT} to decode standard Base64.
+     *
+     * @throws IllegalArgumentException if the input contains
+     * incorrect padding
+     */
+    public static byte[] decode(byte[] input, int flags) {
+        return decode(input, 0, input.length, flags);
+    }
+
+    /**
+     * Decode the Base64-encoded data in input and return the data in
+     * a new byte array.
+     *
+     * <p>The padding '=' characters at the end are considered optional, but
+     * if any are present, there must be the correct number of them.
+     *
+     * @param input  the data to decode
+     * @param offset the position within the input array at which to start
+     * @param len    the number of bytes of input to decode
+     * @param flags  controls certain features of the decoded output.
+     *               Pass {@code DEFAULT} to decode standard Base64.
+     *
+     * @throws IllegalArgumentException if the input contains
+     * incorrect padding
+     */
+    public static byte[] decode(byte[] input, int offset, int len, int flags) {
+        // Allocate space for the most data the input could represent.
+        // (It could contain less if it contains whitespace, etc.)
+        Decoder decoder = new Decoder(flags, new byte[len*3/4]);
+
+        if (!decoder.process(input, offset, len, true)) {
+            throw new IllegalArgumentException("bad base-64");
+        }
+
+        // Maybe we got lucky and allocated exactly enough output space.
+        if (decoder.op == decoder.output.length) {
+            return decoder.output;
+        }
+
+        // Need to shorten the array, so allocate a new one of the
+        // right size and copy.
+        byte[] temp = new byte[decoder.op];
+        System.arraycopy(decoder.output, 0, temp, 0, decoder.op);
+        return temp;
+    }
+
+    /* package */ static class Decoder extends Coder {
+        /**
+         * Lookup table for turning bytes into their position in the
+         * Base64 alphabet.
+         */
+        private static final int DECODE[] = {
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+            -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+            15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+            41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        };
+
+        /**
+         * Decode lookup table for the "web safe" variant (RFC 3548
+         * sec. 4) where - and _ replace + and /.
+         */
+        private static final int DECODE_WEBSAFE[] = {
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
+            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+            -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+            15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
+            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+            41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        };
+
+        /** Non-data values in the DECODE arrays. */
+        private static final int SKIP = -1;
+        private static final int EQUALS = -2;
+
+        /**
+         * States 0-3 are reading through the next input tuple.
+         * State 4 is having read one '=' and expecting exactly
+         * one more.
+         * State 5 is expecting no more data or padding characters
+         * in the input.
+         * State 6 is the error state; an error has been detected
+         * in the input and no future input can "fix" it.
+         */
+        private int state;   // state number (0 to 6)
+        private int value;
+
+        final private int[] alphabet;
+
+        public Decoder(int flags, byte[] output) {
+            this.output = output;
+
+            alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE;
+            state = 0;
+            value = 0;
+        }
+
+        /**
+         * @return an overestimate for the number of bytes {@code
+         * len} bytes could decode to.
+         */
+        public int maxOutputSize(int len) {
+            return len * 3/4 + 10;
+        }
+
+        /**
+         * Decode another block of input data.
+         *
+         * @return true if the state machine is still healthy.  false if
+         *         bad base-64 data has been detected in the input stream.
+         */
+        public boolean process(byte[] input, int offset, int len, boolean finish) {
+            if (this.state == 6) return false;
+
+            int p = offset;
+            len += offset;
+
+            // Using local variables makes the decoder about 12%
+            // faster than if we manipulate the member variables in
+            // the loop.  (Even alphabet makes a measurable
+            // difference, which is somewhat surprising to me since
+            // the member variable is final.)
+            int state = this.state;
+            int value = this.value;
+            int op = 0;
+            final byte[] output = this.output;
+            final int[] alphabet = this.alphabet;
+
+            while (p < len) {
+                // Try the fast path:  we're starting a new tuple and the
+                // next four bytes of the input stream are all data
+                // bytes.  This corresponds to going through states
+                // 0-1-2-3-0.  We expect to use this method for most of
+                // the data.
+                //
+                // If any of the next four bytes of input are non-data
+                // (whitespace, etc.), value will end up negative.  (All
+                // the non-data values in decode are small negative
+                // numbers, so shifting any of them up and or'ing them
+                // together will result in a value with its top bit set.)
+                //
+                // You can remove this whole block and the output should
+                // be the same, just slower.
+                if (state == 0) {
+                    while (p+4 <= len &&
+                           (value = ((alphabet[input[p] & 0xff] << 18) |
+                                     (alphabet[input[p+1] & 0xff] << 12) |
+                                     (alphabet[input[p+2] & 0xff] << 6) |
+                                     (alphabet[input[p+3] & 0xff]))) >= 0) {
+                        output[op+2] = (byte) value;
+                        output[op+1] = (byte) (value >> 8);
+                        output[op] = (byte) (value >> 16);
+                        op += 3;
+                        p += 4;
+                    }
+                    if (p >= len) break;
+                }
+
+                // The fast path isn't available -- either we've read a
+                // partial tuple, or the next four input bytes aren't all
+                // data, or whatever.  Fall back to the slower state
+                // machine implementation.
+
+                int d = alphabet[input[p++] & 0xff];
+
+                switch (state) {
+                case 0:
+                    if (d >= 0) {
+                        value = d;
+                        ++state;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 1:
+                    if (d >= 0) {
+                        value = (value << 6) | d;
+                        ++state;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 2:
+                    if (d >= 0) {
+                        value = (value << 6) | d;
+                        ++state;
+                    } else if (d == EQUALS) {
+                        // Emit the last (partial) output tuple;
+                        // expect exactly one more padding character.
+                        output[op++] = (byte) (value >> 4);
+                        state = 4;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 3:
+                    if (d >= 0) {
+                        // Emit the output triple and return to state 0.
+                        value = (value << 6) | d;
+                        output[op+2] = (byte) value;
+                        output[op+1] = (byte) (value >> 8);
+                        output[op] = (byte) (value >> 16);
+                        op += 3;
+                        state = 0;
+                    } else if (d == EQUALS) {
+                        // Emit the last (partial) output tuple;
+                        // expect no further data or padding characters.
+                        output[op+1] = (byte) (value >> 2);
+                        output[op] = (byte) (value >> 10);
+                        op += 2;
+                        state = 5;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 4:
+                    if (d == EQUALS) {
+                        ++state;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 5:
+                    if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+                }
+            }
+
+            if (!finish) {
+                // We're out of input, but a future call could provide
+                // more.
+                this.state = state;
+                this.value = value;
+                this.op = op;
+                return true;
+            }
+
+            // Done reading input.  Now figure out where we are left in
+            // the state machine and finish up.
+
+            switch (state) {
+            case 0:
+                // Output length is a multiple of three.  Fine.
+                break;
+            case 1:
+                // Read one extra input byte, which isn't enough to
+                // make another output byte.  Illegal.
+                this.state = 6;
+                return false;
+            case 2:
+                // Read two extra input bytes, enough to emit 1 more
+                // output byte.  Fine.
+                output[op++] = (byte) (value >> 4);
+                break;
+            case 3:
+                // Read three extra input bytes, enough to emit 2 more
+                // output bytes.  Fine.
+                output[op++] = (byte) (value >> 10);
+                output[op++] = (byte) (value >> 2);
+                break;
+            case 4:
+                // Read one padding '=' when we expected 2.  Illegal.
+                this.state = 6;
+                return false;
+            case 5:
+                // Read all the padding '='s we expected and no more.
+                // Fine.
+                break;
+            }
+
+            this.state = state;
+            this.op = op;
+            return true;
+        }
+    }
+
+    //  --------------------------------------------------------
+    //  encoding
+    //  --------------------------------------------------------
+
+    /**
+     * Base64-encode the given data and return a newly allocated
+     * String with the result.
+     *
+     * @param input  the data to encode
+     * @param flags  controls certain features of the encoded output.
+     *               Passing {@code DEFAULT} results in output that
+     *               adheres to RFC 2045.
+     */
+    public static String encodeToString(byte[] input, int flags) {
+        try {
+            return new String(encode(input, flags), "US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            // US-ASCII is guaranteed to be available.
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * Base64-encode the given data and return a newly allocated
+     * String with the result.
+     *
+     * @param input  the data to encode
+     * @param offset the position within the input array at which to
+     *               start
+     * @param len    the number of bytes of input to encode
+     * @param flags  controls certain features of the encoded output.
+     *               Passing {@code DEFAULT} results in output that
+     *               adheres to RFC 2045.
+     */
+    public static String encodeToString(byte[] input, int offset, int len, int flags) {
+        try {
+            return new String(encode(input, offset, len, flags), "US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            // US-ASCII is guaranteed to be available.
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * Base64-encode the given data and return a newly allocated
+     * byte[] with the result.
+     *
+     * @param input  the data to encode
+     * @param flags  controls certain features of the encoded output.
+     *               Passing {@code DEFAULT} results in output that
+     *               adheres to RFC 2045.
+     */
+    public static byte[] encode(byte[] input, int flags) {
+        return encode(input, 0, input.length, flags);
+    }
+
+    /**
+     * Base64-encode the given data and return a newly allocated
+     * byte[] with the result.
+     *
+     * @param input  the data to encode
+     * @param offset the position within the input array at which to
+     *               start
+     * @param len    the number of bytes of input to encode
+     * @param flags  controls certain features of the encoded output.
+     *               Passing {@code DEFAULT} results in output that
+     *               adheres to RFC 2045.
+     */
+    public static byte[] encode(byte[] input, int offset, int len, int flags) {
+        Encoder encoder = new Encoder(flags, null);
+
+        // Compute the exact length of the array we will produce.
+        int output_len = len / 3 * 4;
+
+        // Account for the tail of the data and the padding bytes, if any.
+        if (encoder.do_padding) {
+            if (len % 3 > 0) {
+                output_len += 4;
+            }
+        } else {
+            switch (len % 3) {
+                case 0: break;
+                case 1: output_len += 2; break;
+                case 2: output_len += 3; break;
+            }
+        }
+
+        // Account for the newlines, if any.
+        if (encoder.do_newline && len > 0) {
+            output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) *
+                (encoder.do_cr ? 2 : 1);
+        }
+
+        encoder.output = new byte[output_len];
+        encoder.process(input, offset, len, true);
+
+        assert encoder.op == output_len;
+
+        return encoder.output;
+    }
+
+    /* package */ static class Encoder extends Coder {
+        /**
+         * Emit a new line every this many output tuples.  Corresponds to
+         * a 76-character line length (the maximum allowable according to
+         * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>).
+         */
+        public static final int LINE_GROUPS = 19;
+
+        /**
+         * Lookup table for turning Base64 alphabet positions (6 bits)
+         * into output bytes.
+         */
+        private static final byte ENCODE[] = {
+            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
+        };
+
+        /**
+         * Lookup table for turning Base64 alphabet positions (6 bits)
+         * into output bytes.
+         */
+        private static final byte ENCODE_WEBSAFE[] = {
+            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
+        };
+
+        final private byte[] tail;
+        /* package */ int tailLen;
+        private int count;
+
+        final public boolean do_padding;
+        final public boolean do_newline;
+        final public boolean do_cr;
+        final private byte[] alphabet;
+
+        public Encoder(int flags, byte[] output) {
+            this.output = output;
+
+            do_padding = (flags & NO_PADDING) == 0;
+            do_newline = (flags & NO_WRAP) == 0;
+            do_cr = (flags & CRLF) != 0;
+            alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
+
+            tail = new byte[2];
+            tailLen = 0;
+
+            count = do_newline ? LINE_GROUPS : -1;
+        }
+
+        /**
+         * @return an overestimate for the number of bytes {@code
+         * len} bytes could encode to.
+         */
+        public int maxOutputSize(int len) {
+            return len * 8/5 + 10;
+        }
+
+        public boolean process(byte[] input, int offset, int len, boolean finish) {
+            // Using local variables makes the encoder about 9% faster.
+            final byte[] alphabet = this.alphabet;
+            final byte[] output = this.output;
+            int op = 0;
+            int count = this.count;
+
+            int p = offset;
+            len += offset;
+            int v = -1;
+
+            // First we need to concatenate the tail of the previous call
+            // with any input bytes available now and see if we can empty
+            // the tail.
+
+            switch (tailLen) {
+                case 0:
+                    // There was no tail.
+                    break;
+
+                case 1:
+                    if (p+2 <= len) {
+                        // A 1-byte tail with at least 2 bytes of
+                        // input available now.
+                        v = ((tail[0] & 0xff) << 16) |
+                            ((input[p++] & 0xff) << 8) |
+                            (input[p++] & 0xff);
+                        tailLen = 0;
+                    };
+                    break;
+
+                case 2:
+                    if (p+1 <= len) {
+                        // A 2-byte tail with at least 1 byte of input.
+                        v = ((tail[0] & 0xff) << 16) |
+                            ((tail[1] & 0xff) << 8) |
+                            (input[p++] & 0xff);
+                        tailLen = 0;
+                    }
+                    break;
+            }
+
+            if (v != -1) {
+                output[op++] = alphabet[(v >> 18) & 0x3f];
+                output[op++] = alphabet[(v >> 12) & 0x3f];
+                output[op++] = alphabet[(v >> 6) & 0x3f];
+                output[op++] = alphabet[v & 0x3f];
+                if (--count == 0) {
+                    if (do_cr) output[op++] = '\r';
+                    output[op++] = '\n';
+                    count = LINE_GROUPS;
+                }
+            }
+
+            // At this point either there is no tail, or there are fewer
+            // than 3 bytes of input available.
+
+            // The main loop, turning 3 input bytes into 4 output bytes on
+            // each iteration.
+            while (p+3 <= len) {
+                v = ((input[p] & 0xff) << 16) |
+                    ((input[p+1] & 0xff) << 8) |
+                    (input[p+2] & 0xff);
+                output[op] = alphabet[(v >> 18) & 0x3f];
+                output[op+1] = alphabet[(v >> 12) & 0x3f];
+                output[op+2] = alphabet[(v >> 6) & 0x3f];
+                output[op+3] = alphabet[v & 0x3f];
+                p += 3;
+                op += 4;
+                if (--count == 0) {
+                    if (do_cr) output[op++] = '\r';
+                    output[op++] = '\n';
+                    count = LINE_GROUPS;
+                }
+            }
+
+            if (finish) {
+                // Finish up the tail of the input.  Note that we need to
+                // consume any bytes in tail before any bytes
+                // remaining in input; there should be at most two bytes
+                // total.
+
+                if (p-tailLen == len-1) {
+                    int t = 0;
+                    v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4;
+                    tailLen -= t;
+                    output[op++] = alphabet[(v >> 6) & 0x3f];
+                    output[op++] = alphabet[v & 0x3f];
+                    if (do_padding) {
+                        output[op++] = '=';
+                        output[op++] = '=';
+                    }
+                    if (do_newline) {
+                        if (do_cr) output[op++] = '\r';
+                        output[op++] = '\n';
+                    }
+                } else if (p-tailLen == len-2) {
+                    int t = 0;
+                    v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) |
+                        (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2);
+                    tailLen -= t;
+                    output[op++] = alphabet[(v >> 12) & 0x3f];
+                    output[op++] = alphabet[(v >> 6) & 0x3f];
+                    output[op++] = alphabet[v & 0x3f];
+                    if (do_padding) {
+                        output[op++] = '=';
+                    }
+                    if (do_newline) {
+                        if (do_cr) output[op++] = '\r';
+                        output[op++] = '\n';
+                    }
+                } else if (do_newline && op > 0 && count != LINE_GROUPS) {
+                    if (do_cr) output[op++] = '\r';
+                    output[op++] = '\n';
+                }
+
+                assert tailLen == 0;
+                assert p == len;
+            } else {
+                // Save the leftovers in tail to be consumed on the next
+                // call to encodeInternal.
+
+                if (p == len-1) {
+                    tail[tailLen++] = input[p];
+                } else if (p == len-2) {
+                    tail[tailLen++] = input[p];
+                    tail[tailLen++] = input[p+1];
+                }
+            }
+
+            this.op = op;
+            this.count = count;
+
+            return true;
+        }
+    }
+
+    @UnsupportedAppUsage
+    private Base64() { }   // don't instantiate
+}
diff --git a/android/util/Base64DataException.java b/android/util/Base64DataException.java
new file mode 100644
index 0000000..de12ee1
--- /dev/null
+++ b/android/util/Base64DataException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.util;
+
+import java.io.IOException;
+
+/**
+ * This exception is thrown by {@link Base64InputStream} or {@link Base64OutputStream}
+ * when an error is detected in the data being decoded.  This allows problems with the base64 data
+ * to be disambiguated from errors in the underlying streams (e.g. actual connection errors.)
+ */
+public class Base64DataException extends IOException {
+    public Base64DataException(String detailMessage) {
+        super(detailMessage);
+    }
+}
diff --git a/android/util/Base64InputStream.java b/android/util/Base64InputStream.java
new file mode 100644
index 0000000..9eba5b5
--- /dev/null
+++ b/android/util/Base64InputStream.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2010 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;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An InputStream that does Base64 decoding on the data read through
+ * it.
+ */
+public class Base64InputStream extends FilterInputStream {
+    private final Base64.Coder coder;
+
+    private static byte[] EMPTY = new byte[0];
+
+    private static final int BUFFER_SIZE = 2048;
+    private boolean eof;
+    private byte[] inputBuffer;
+    private int outputStart;
+    private int outputEnd;
+
+    /**
+     * An InputStream that performs Base64 decoding on the data read
+     * from the wrapped stream.
+     *
+     * @param in the InputStream to read the source data from
+     * @param flags bit flags for controlling the decoder; see the
+     *        constants in {@link Base64}
+     */
+    public Base64InputStream(InputStream in, int flags) {
+        this(in, flags, false);
+    }
+
+    /**
+     * Performs Base64 encoding or decoding on the data read from the
+     * wrapped InputStream.
+     *
+     * @param in the InputStream to read the source data from
+     * @param flags bit flags for controlling the decoder; see the
+     *        constants in {@link Base64}
+     * @param encode true to encode, false to decode
+     *
+     * @hide
+     */
+    public Base64InputStream(InputStream in, int flags, boolean encode) {
+        super(in);
+        eof = false;
+        inputBuffer = new byte[BUFFER_SIZE];
+        if (encode) {
+            coder = new Base64.Encoder(flags, null);
+        } else {
+            coder = new Base64.Decoder(flags, null);
+        }
+        coder.output = new byte[coder.maxOutputSize(BUFFER_SIZE)];
+        outputStart = 0;
+        outputEnd = 0;
+    }
+
+    public boolean markSupported() {
+        return false;
+    }
+
+    public void mark(int readlimit) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void reset() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void close() throws IOException {
+        in.close();
+        inputBuffer = null;
+    }
+
+    public int available() {
+        return outputEnd - outputStart;
+    }
+
+    public long skip(long n) throws IOException {
+        if (outputStart >= outputEnd) {
+            refill();
+        }
+        if (outputStart >= outputEnd) {
+            return 0;
+        }
+        long bytes = Math.min(n, outputEnd-outputStart);
+        outputStart += bytes;
+        return bytes;
+    }
+
+    public int read() throws IOException {
+        if (outputStart >= outputEnd) {
+            refill();
+        }
+        if (outputStart >= outputEnd) {
+            return -1;
+        } else {
+            return coder.output[outputStart++] & 0xff;
+        }
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (outputStart >= outputEnd) {
+            refill();
+        }
+        if (outputStart >= outputEnd) {
+            return -1;
+        }
+        int bytes = Math.min(len, outputEnd-outputStart);
+        System.arraycopy(coder.output, outputStart, b, off, bytes);
+        outputStart += bytes;
+        return bytes;
+    }
+
+    /**
+     * Read data from the input stream into inputBuffer, then
+     * decode/encode it into the empty coder.output, and reset the
+     * outputStart and outputEnd pointers.
+     */
+    private void refill() throws IOException {
+        if (eof) return;
+        int bytesRead = in.read(inputBuffer);
+        boolean success;
+        if (bytesRead == -1) {
+            eof = true;
+            success = coder.process(EMPTY, 0, 0, true);
+        } else {
+            success = coder.process(inputBuffer, 0, bytesRead, false);
+        }
+        if (!success) {
+            throw new Base64DataException("bad base-64");
+        }
+        outputEnd = coder.op;
+        outputStart = 0;
+    }
+}
diff --git a/android/util/Base64OutputStream.java b/android/util/Base64OutputStream.java
new file mode 100644
index 0000000..48fadeb
--- /dev/null
+++ b/android/util/Base64OutputStream.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2010 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An OutputStream that does Base64 encoding on the data written to
+ * it, writing the resulting data to another OutputStream.
+ */
+public class Base64OutputStream extends FilterOutputStream {
+    private final Base64.Coder coder;
+    private final int flags;
+
+    private byte[] buffer = null;
+    private int bpos = 0;
+
+    private static byte[] EMPTY = new byte[0];
+
+    /**
+     * Performs Base64 encoding on the data written to the stream,
+     * writing the encoded data to another OutputStream.
+     *
+     * @param out the OutputStream to write the encoded data to
+     * @param flags bit flags for controlling the encoder; see the
+     *        constants in {@link Base64}
+     */
+    public Base64OutputStream(OutputStream out, int flags) {
+        this(out, flags, true);
+    }
+
+    /**
+     * Performs Base64 encoding or decoding on the data written to the
+     * stream, writing the encoded/decoded data to another
+     * OutputStream.
+     *
+     * @param out the OutputStream to write the encoded data to
+     * @param flags bit flags for controlling the encoder; see the
+     *        constants in {@link Base64}
+     * @param encode true to encode, false to decode
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public Base64OutputStream(OutputStream out, int flags, boolean encode) {
+        super(out);
+        this.flags = flags;
+        if (encode) {
+            coder = new Base64.Encoder(flags, null);
+        } else {
+            coder = new Base64.Decoder(flags, null);
+        }
+    }
+
+    public void write(int b) throws IOException {
+        // To avoid invoking the encoder/decoder routines for single
+        // bytes, we buffer up calls to write(int) in an internal
+        // byte array to transform them into writes of decently-sized
+        // arrays.
+
+        if (buffer == null) {
+            buffer = new byte[1024];
+        }
+        if (bpos >= buffer.length) {
+            // internal buffer full; write it out.
+            internalWrite(buffer, 0, bpos, false);
+            bpos = 0;
+        }
+        buffer[bpos++] = (byte) b;
+    }
+
+    /**
+     * Flush any buffered data from calls to write(int).  Needed
+     * before doing a write(byte[], int, int) or a close().
+     */
+    private void flushBuffer() throws IOException {
+        if (bpos > 0) {
+            internalWrite(buffer, 0, bpos, false);
+            bpos = 0;
+        }
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+        if (len <= 0) return;
+        flushBuffer();
+        internalWrite(b, off, len, false);
+    }
+
+    public void close() throws IOException {
+        IOException thrown = null;
+        try {
+            flushBuffer();
+            internalWrite(EMPTY, 0, 0, true);
+        } catch (IOException e) {
+            thrown = e;
+        }
+
+        try {
+            if ((flags & Base64.NO_CLOSE) == 0) {
+                out.close();
+            } else {
+                out.flush();
+            }
+        } catch (IOException e) {
+            if (thrown == null) {
+                thrown = e;
+            } else {
+                thrown.addSuppressed(e);
+            }
+        }
+
+        if (thrown != null) {
+            throw thrown;
+        }
+    }
+
+    /**
+     * Write the given bytes to the encoder/decoder.
+     *
+     * @param finish true if this is the last batch of input, to cause
+     *        encoder/decoder state to be finalized.
+     */
+    private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException {
+        coder.output = embiggen(coder.output, coder.maxOutputSize(len));
+        if (!coder.process(b, off, len, finish)) {
+            throw new Base64DataException("bad base-64");
+        }
+        out.write(coder.output, 0, coder.op);
+    }
+
+    /**
+     * If b.length is at least len, return b.  Otherwise return a new
+     * byte array of length len.
+     */
+    private byte[] embiggen(byte[] b, int len) {
+        if (b == null || b.length < len) {
+            return new byte[len];
+        } else {
+            return b;
+        }
+    }
+}
diff --git a/android/util/BridgeXmlPullAttributes.java b/android/util/BridgeXmlPullAttributes.java
new file mode 100644
index 0000000..2f4b0ca
--- /dev/null
+++ b/android/util/BridgeXmlPullAttributes.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2008 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;
+
+import com.android.ide.common.rendering.api.AttrResourceValue;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.internal.util.XmlUtils;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
+import com.android.layoutlib.bridge.android.XmlPullParserResolver;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.resources.ResourceType;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser
+ */
+public class BridgeXmlPullAttributes extends XmlPullAttributes implements ResolvingAttributeSet {
+
+    interface EnumValueSupplier {
+        @Nullable
+        Map<String, Integer> getEnumValues(
+                @NonNull ResourceNamespace namespace, @NonNull String attrName);
+    }
+
+    private final BridgeContext mContext;
+    private final ResourceNamespace mXmlFileResourceNamespace;
+    private final ResourceNamespace.Resolver mResourceNamespaceResolver;
+    private final Function<String, Map<String, Integer>> mFrameworkEnumValueSupplier;
+    private final EnumValueSupplier mProjectEnumValueSupplier;
+
+    // VisibleForTesting
+    BridgeXmlPullAttributes(
+            @NonNull XmlPullParser parser,
+            @NonNull BridgeContext context,
+            @NonNull ResourceNamespace xmlFileResourceNamespace,
+            @NonNull Function<String, Map<String, Integer>> frameworkEnumValueSupplier,
+            @NonNull EnumValueSupplier projectEnumValueSupplier) {
+        super(parser);
+        mContext = context;
+        mFrameworkEnumValueSupplier = frameworkEnumValueSupplier;
+        mProjectEnumValueSupplier = projectEnumValueSupplier;
+        mXmlFileResourceNamespace = xmlFileResourceNamespace;
+        mResourceNamespaceResolver =
+                new XmlPullParserResolver(
+                        mParser, context.getLayoutlibCallback().getImplicitNamespaces());
+    }
+
+    public BridgeXmlPullAttributes(
+            @NonNull XmlPullParser parser,
+            @NonNull BridgeContext context,
+            @NonNull ResourceNamespace xmlFileResourceNamespace) {
+        this(parser, context, xmlFileResourceNamespace, Bridge::getEnumValues, (ns, attrName) -> {
+            ResourceValue attr =
+                    context.getRenderResources().getUnresolvedResource(
+                            ResourceReference.attr(ns, attrName));
+            return attr instanceof AttrResourceValue
+                    ? ((AttrResourceValue) attr).getAttributeValues()
+                    : null;
+        });
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.util.XmlPullAttributes#getAttributeNameResource(int)
+     *
+     * This methods must return com.android.internal.R.attr.<name> matching
+     * the name of the attribute.
+     * It returns 0 if it doesn't find anything.
+     */
+    @Override
+    public int getAttributeNameResource(int index) {
+        // get the attribute name.
+        String name = getAttributeName(index);
+
+        // get the attribute namespace
+        String ns = mParser.getAttributeNamespace(index);
+
+        if (BridgeConstants.NS_RESOURCES.equals(ns)) {
+            return Bridge.getResourceId(ResourceType.ATTR, name);
+        }
+
+        // this is not an attribute in the android namespace, we query the customviewloader, if
+        // the namespaces match.
+        if (mContext.getLayoutlibCallback().getNamespace().equals(ns)) {
+            // TODO(namespaces): cache the namespace objects.
+            ResourceNamespace namespace = ResourceNamespace.fromNamespaceUri(ns);
+            if (namespace != null) {
+                return mContext.getLayoutlibCallback().getOrGenerateResourceId(
+                        ResourceReference.attr(namespace, name));
+            }
+        }
+
+        return 0;
+    }
+
+    @Override
+    public int getAttributeListValue(String namespace, String attribute,
+            String[] options, int defaultValue) {
+        String value = getAttributeValue(namespace, attribute);
+        if (value != null) {
+            ResourceValue r = getResourceValue(value);
+
+            if (r != null) {
+                value = r.getValue();
+            }
+
+            return XmlUtils.convertValueToList(value, options, defaultValue);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public boolean getAttributeBooleanValue(String namespace, String attribute,
+            boolean defaultValue) {
+        String value = getAttributeValue(namespace, attribute);
+        if (value != null) {
+            ResourceValue r = getResourceValue(value);
+
+            if (r != null) {
+                value = r.getValue();
+            }
+
+            return XmlUtils.convertValueToBoolean(value, defaultValue);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
+        String value = getAttributeValue(namespace, attribute);
+
+        return resolveResourceValue(value, defaultValue);
+    }
+
+    @Override
+    public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
+        String value = getAttributeValue(namespace, attribute);
+        if (value == null) {
+            return defaultValue;
+        }
+
+        ResourceValue r = getResourceValue(value);
+
+        if (r != null) {
+            value = r.getValue();
+        }
+
+        if (value.charAt(0) == '#') {
+            return ResourceHelper.getColor(value);
+        }
+
+        try {
+            return XmlUtils.convertValueToInt(value, defaultValue);
+        } catch (NumberFormatException e) {
+            // This is probably an enum
+            Map<String, Integer> enumValues = null;
+            if (BridgeConstants.NS_RESOURCES.equals(namespace)) {
+                enumValues = mFrameworkEnumValueSupplier.apply(attribute);
+            } else {
+                ResourceNamespace attrNamespace = ResourceNamespace.fromNamespaceUri(namespace);
+                if (attrNamespace != null) {
+                    enumValues = mProjectEnumValueSupplier.getEnumValues(attrNamespace, attribute);
+                }
+            }
+
+            Integer enumValue = enumValues != null ? enumValues.get(value) : null;
+            if (enumValue != null) {
+                return enumValue;
+            }
+
+            // We weren't able to find the enum int value
+            throw e;
+        }
+    }
+
+    @Override
+    public int getAttributeUnsignedIntValue(String namespace, String attribute,
+            int defaultValue) {
+        String value = getAttributeValue(namespace, attribute);
+        if (value != null) {
+            ResourceValue r = getResourceValue(value);
+
+            if (r != null) {
+                value = r.getValue();
+            }
+
+            return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public float getAttributeFloatValue(String namespace, String attribute,
+            float defaultValue) {
+        String s = getAttributeValue(namespace, attribute);
+        if (s != null) {
+            ResourceValue r = getResourceValue(s);
+
+            if (r != null) {
+                s = r.getValue();
+            }
+
+            return Float.parseFloat(s);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public int getAttributeListValue(int index,
+            String[] options, int defaultValue) {
+        return XmlUtils.convertValueToList(
+            getAttributeValue(index), options, defaultValue);
+    }
+
+    @Override
+    public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+        String value = getAttributeValue(index);
+        if (value != null) {
+            ResourceValue r = getResourceValue(value);
+
+            if (r != null) {
+                value = r.getValue();
+            }
+
+            return XmlUtils.convertValueToBoolean(value, defaultValue);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public int getAttributeResourceValue(int index, int defaultValue) {
+        String value = getAttributeValue(index);
+
+        return resolveResourceValue(value, defaultValue);
+    }
+
+    @Override
+    public int getAttributeIntValue(int index, int defaultValue) {
+        return getAttributeIntValue(
+                mParser.getAttributeNamespace(index), getAttributeName(index), defaultValue);
+    }
+
+    @Override
+    public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+        String value = getAttributeValue(index);
+        if (value != null) {
+            ResourceValue r = getResourceValue(value);
+
+            if (r != null) {
+                value = r.getValue();
+            }
+
+            return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public float getAttributeFloatValue(int index, float defaultValue) {
+        String s = getAttributeValue(index);
+        if (s != null) {
+            ResourceValue r = getResourceValue(s);
+
+            if (r != null) {
+                s = r.getValue();
+            }
+
+            return Float.parseFloat(s);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    @Nullable
+    public ResourceValue getResolvedAttributeValue(@Nullable String namespace,
+            @NonNull String name) {
+        String s = getAttributeValue(namespace, name);
+        return s == null ? null : getResourceValue(s);
+    }
+
+    // -- private helper methods
+
+    /**
+     * Returns a resolved {@link ResourceValue} from a given value.
+     */
+    private ResourceValue getResourceValue(String value) {
+        // now look for this particular value
+        return mContext.getRenderResources().resolveResValue(
+                new UnresolvedResourceValue(
+                        value, mXmlFileResourceNamespace, mResourceNamespaceResolver));
+    }
+
+    /**
+     * Resolves and return a value to its associated integer.
+     */
+    private int resolveResourceValue(String value, int defaultValue) {
+        ResourceValue resource = getResourceValue(value);
+        if (resource != null) {
+            return resource.isFramework() ?
+                    Bridge.getResourceId(resource.getResourceType(), resource.getName()) :
+                    mContext.getLayoutlibCallback().getOrGenerateResourceId(resource.asReference());
+        }
+
+        return defaultValue;
+    }
+}
diff --git a/android/util/CloseGuard.java b/android/util/CloseGuard.java
new file mode 100644
index 0000000..ba504a3
--- /dev/null
+++ b/android/util/CloseGuard.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.annotation.NonNull;
+
+/**
+ * CloseGuard is a mechanism for flagging implicit finalizer cleanup of
+ * resources that should have been cleaned up by explicit close
+ * methods (aka "explicit termination methods" in Effective Java).
+ * <p>
+ * A simple example: <pre>   {@code
+ *   class Foo {
+ *
+ *       private final CloseGuard guard = new CloseGuard();
+ *
+ *       ...
+ *
+ *       public Foo() {
+ *           ...;
+ *           guard.open("cleanup");
+ *       }
+ *
+ *       public void cleanup() {
+ *          guard.close();
+ *          ...;
+ *          if (Build.VERSION.SDK_INT >= 28) {
+ *              Reference.reachabilityFence(this);
+ *          }
+ *          // For full correctness in the absence of a close() call, other methods may also need
+ *          // reachabilityFence() calls.
+ *       }
+ *
+ *       protected void finalize() throws Throwable {
+ *           try {
+ *               // Note that guard could be null if the constructor threw.
+ *               if (guard != null) {
+ *                   guard.warnIfOpen();
+ *               }
+ *               cleanup();
+ *           } finally {
+ *               super.finalize();
+ *           }
+ *       }
+ *   }
+ * }</pre>
+ *
+ * In usage where the resource to be explicitly cleaned up is
+ * allocated after object construction, CloseGuard protection can
+ * be deferred. For example: <pre>   {@code
+ *   class Bar {
+ *
+ *       private final CloseGuard guard = new CloseGuard();
+ *
+ *       ...
+ *
+ *       public Bar() {
+ *           ...;
+ *       }
+ *
+ *       public void connect() {
+ *          ...;
+ *          guard.open("cleanup");
+ *       }
+ *
+ *       public void cleanup() {
+ *          guard.close();
+ *          ...;
+ *          if (Build.VERSION.SDK_INT >= 28) {
+ *              Reference.reachabilityFence(this);
+ *          }
+ *          // For full correctness in the absence of a close() call, other methods may also need
+ *          // reachabilityFence() calls.
+ *       }
+ *
+ *       protected void finalize() throws Throwable {
+ *           try {
+ *               // Note that guard could be null if the constructor threw.
+ *               if (guard != null) {
+ *                   guard.warnIfOpen();
+ *               }
+ *               cleanup();
+ *           } finally {
+ *               super.finalize();
+ *           }
+ *       }
+ *   }
+ * }</pre>
+ *
+ * When used in a constructor, calls to {@code open} should occur at
+ * the end of the constructor since an exception that would cause
+ * abrupt termination of the constructor will mean that the user will
+ * not have a reference to the object to cleanup explicitly. When used
+ * in a method, the call to {@code open} should occur just after
+ * resource acquisition.
+ */
+public final class CloseGuard {
+    private final dalvik.system.CloseGuard mImpl;
+
+    /**
+     * Constructs a new CloseGuard instance.
+     * {@link #open(String)} can be used to set up the instance to warn on failure to close.
+     */
+    public CloseGuard() {
+        mImpl = dalvik.system.CloseGuard.get();
+    }
+
+    /**
+     * Initializes the instance with a warning that the caller should have explicitly called the
+     * {@code closeMethodName} method instead of relying on finalization.
+     *
+     * @param closeMethodName non-null name of explicit termination method. Printed by warnIfOpen.
+     * @throws NullPointerException if closeMethodName is null.
+     */
+    public void open(@NonNull String closeMethodName) {
+        mImpl.open(closeMethodName);
+    }
+
+    /** Marks this CloseGuard instance as closed to avoid warnings on finalization. */
+    public void close() {
+        mImpl.close();
+    }
+
+    /**
+     * Logs a warning if the caller did not properly cleanup by calling an explicit close method
+     * before finalization.
+     */
+    public void warnIfOpen() {
+        mImpl.warnIfOpen();
+    }
+}
diff --git a/android/util/Config.java b/android/util/Config.java
new file mode 100644
index 0000000..70dc9aa
--- /dev/null
+++ b/android/util/Config.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * @deprecated This class is not useful, it just returns the same value for
+ * all constants, and has always done this.  Do not use it.
+ */
+@Deprecated
+public final class Config {
+    /** @hide */ public Config() {}
+
+    /**
+     * @deprecated Always false.
+     */
+    @Deprecated
+    public static final boolean DEBUG = false;
+
+    /**
+     * @deprecated Always true.
+     */
+    @Deprecated
+    public static final boolean RELEASE = true;
+
+    /**
+     * @deprecated Always false.
+     */
+    @Deprecated
+    public static final boolean PROFILE = false;
+
+    /**
+     * @deprecated Always false.
+     */
+    @Deprecated
+    public static final boolean LOGV = false;
+
+    /**
+     * @deprecated Always true.
+     */
+    @Deprecated
+    public static final boolean LOGD = true;
+}
diff --git a/android/util/ContainerHelpers.java b/android/util/ContainerHelpers.java
new file mode 100644
index 0000000..4e5fefb
--- /dev/null
+++ b/android/util/ContainerHelpers.java
@@ -0,0 +1,59 @@
+/*
+ * 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 {
+
+    // 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/android/util/DataUnit.java b/android/util/DataUnit.java
new file mode 100644
index 0000000..cf045b8
--- /dev/null
+++ b/android/util/DataUnit.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@code DataUnit} represents data sizes at a given unit of granularity and
+ * provides utility methods to convert across units.
+ * <p>
+ * Note that both SI units (powers of 10) and IEC units (powers of 2) are
+ * supported, and you'll need to pick the correct one for your use-case. For
+ * example, Wikipedia defines a "kilobyte" as an SI unit of 1000 bytes, and a
+ * "kibibyte" as an IEC unit of 1024 bytes.
+ * <p>
+ * This design is mirrored after {@link TimeUnit} and {@link ChronoUnit}.
+ *
+ * @hide
+ */
+public enum DataUnit {
+    KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } },
+    MEGABYTES { @Override public long toBytes(long v) { return v * 1_000_000; } },
+    GIGABYTES { @Override public long toBytes(long v) { return v * 1_000_000_000; } },
+    KIBIBYTES { @Override public long toBytes(long v) { return v * 1_024; } },
+    MEBIBYTES { @Override public long toBytes(long v) { return v * 1_048_576; } },
+    GIBIBYTES { @Override public long toBytes(long v) { return v * 1_073_741_824; } };
+
+    public long toBytes(long v) {
+        throw new AbstractMethodError();
+    }
+}
diff --git a/android/util/DayOfMonthCursor.java b/android/util/DayOfMonthCursor.java
new file mode 100644
index 0000000..393b98e
--- /dev/null
+++ b/android/util/DayOfMonthCursor.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2007 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;
+
+/**
+ * Helps control and display a month view of a calendar that has a current
+ * selected day.
+ * <ul>
+ *   <li>Keeps track of current month, day, year</li>
+ *   <li>Keeps track of current cursor position (row, column)</li>
+ *   <li>Provides methods to help display the calendar</li>
+ *   <li>Provides methods to move the cursor up / down / left / right.</li>
+ * </ul>
+ *
+ * This should be used by anyone who presents a month view to users and wishes
+ * to behave consistently with other widgets and apps; if we ever change our
+ * mind about when to flip the month, we can change it here only.
+ *
+ * @hide
+ */
+public class DayOfMonthCursor extends MonthDisplayHelper {
+
+    private int mRow;
+    private int mColumn;
+
+    /**
+     * @param year The initial year.
+     * @param month The initial month.
+     * @param dayOfMonth The initial dayOfMonth.
+     * @param weekStartDay What dayOfMonth of the week the week should start,
+     *   in terms of {@link java.util.Calendar} constants such as
+     *   {@link java.util.Calendar#SUNDAY}.
+     */
+    public DayOfMonthCursor(int year, int month, int dayOfMonth, int weekStartDay) {
+        super(year, month, weekStartDay);
+        mRow = getRowOf(dayOfMonth);
+        mColumn = getColumnOf(dayOfMonth);
+    }
+
+
+    public int getSelectedRow() {
+        return mRow;
+    }
+
+    public int getSelectedColumn() {
+        return mColumn;
+    }
+    
+    public void setSelectedRowColumn(int row, int col) {
+        mRow = row;
+        mColumn = col;
+    }
+
+    public int getSelectedDayOfMonth() {
+        return getDayAt(mRow, mColumn);
+    }
+
+    /**
+     * @return 0 if the selection is in the current month, otherwise -1 or +1
+     * depending on whether the selection is in the first or last row.
+     */
+    public int getSelectedMonthOffset() {
+        if (isWithinCurrentMonth(mRow, mColumn)) {
+            return 0;
+        }
+        if (mRow == 0) {
+            return -1;
+        }
+        return 1;
+    }
+    
+    public void setSelectedDayOfMonth(int dayOfMonth) {
+        mRow = getRowOf(dayOfMonth);
+        mColumn = getColumnOf(dayOfMonth);
+    }
+    
+    public boolean isSelected(int row, int column) {
+        return (mRow == row) && (mColumn == column);
+    }
+
+    /**
+     * Move up one box, potentially flipping to the previous month.
+     * @return Whether the month was flipped to the previous month
+     *   due to the move.
+     */
+    public boolean up() {
+        if (isWithinCurrentMonth(mRow - 1, mColumn)) {
+            // within current month, just move up
+            mRow--;
+            return false;
+        }
+        // flip back to previous month, same column, first position within month
+        previousMonth();
+        mRow = 5;
+        while(!isWithinCurrentMonth(mRow, mColumn)) {
+            mRow--;
+        }
+        return true;
+    }
+
+    /**
+     * Move down one box, potentially flipping to the next month.
+     * @return Whether the month was flipped to the next month
+     *   due to the move.
+     */
+    public boolean down() {
+        if (isWithinCurrentMonth(mRow + 1, mColumn)) {
+            // within current month, just move down
+            mRow++;
+            return false;
+        }
+        // flip to next month, same column, first position within month
+        nextMonth();
+        mRow = 0;
+        while (!isWithinCurrentMonth(mRow, mColumn)) {
+            mRow++;
+        }
+        return true;
+    }
+
+    /**
+     * Move left one box, potentially flipping to the previous month.
+     * @return Whether the month was flipped to the previous month
+     *   due to the move.
+     */
+    public boolean left() {
+        if (mColumn == 0) {
+            mRow--;
+            mColumn = 6;
+        } else {
+            mColumn--;
+        }
+
+        if (isWithinCurrentMonth(mRow, mColumn)) {
+            return false;
+        }
+
+        // need to flip to last day of previous month
+        previousMonth();
+        int lastDay = getNumberOfDaysInMonth();
+        mRow = getRowOf(lastDay);
+        mColumn = getColumnOf(lastDay);
+        return true;
+    }
+
+    /**
+     * Move right one box, potentially flipping to the next month.
+     * @return Whether the month was flipped to the next month
+     *   due to the move.
+     */
+    public boolean right() {
+        if (mColumn == 6) {
+            mRow++;
+            mColumn = 0;
+        } else {
+            mColumn++;
+        }
+
+        if (isWithinCurrentMonth(mRow, mColumn)) {
+            return false;
+        }
+
+        // need to flip to first day of next month
+        nextMonth();
+        mRow = 0;
+        mColumn = 0;
+        while (!isWithinCurrentMonth(mRow, mColumn)) {
+            mColumn++;
+        }
+        return true;
+    }
+
+}
diff --git a/android/util/DebugUtils.java b/android/util/DebugUtils.java
new file mode 100644
index 0000000..6d5e830
--- /dev/null
+++ b/android/util/DebugUtils.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/**
+ * <p>Various utilities for debugging and logging.</p>
+ */
+public class DebugUtils {
+    /** @hide */ public DebugUtils() {}
+
+    /**
+     * <p>Filters objects against the <code>ANDROID_OBJECT_FILTER</code>
+     * environment variable. This environment variable can filter objects
+     * based on their class name and attribute values.</p>
+     *
+     * <p>Here is the syntax for <code>ANDROID_OBJECT_FILTER</code>:</p>
+     *
+     * <p><code>ClassName@attribute1=value1@attribute2=value2...</code></p>
+     *
+     * <p>Examples:</p>
+     * <ul>
+     * <li>Select TextView instances: <code>TextView</code></li>
+     * <li>Select TextView instances of text "Loading" and bottom offset of 22:
+     * <code>TextView@text=Loading.*@bottom=22</code></li>
+     * </ul>
+     *
+     * <p>The class name and the values are regular expressions.</p>
+     *
+     * <p>This class is useful for debugging and logging purpose:</p>
+     * <pre>
+     * if (DEBUG) {
+     *   if (DebugUtils.isObjectSelected(childView) && LOGV_ENABLED) {
+     *     Log.v(TAG, "Object " + childView + " logged!");
+     *   }
+     * }
+     * </pre>
+     *
+     * <p><strong>NOTE</strong>: This method is very expensive as it relies
+     * heavily on regular expressions and reflection. Calls to this method
+     * should always be stripped out of the release binaries and avoided
+     * as much as possible in debug mode.</p>
+     *
+     * @param object any object to match against the ANDROID_OBJECT_FILTER
+     *        environement variable
+     * @return true if object is selected by the ANDROID_OBJECT_FILTER
+     *         environment variable, false otherwise
+     */
+    public static boolean isObjectSelected(Object object) {
+        boolean match = false;
+        String s = System.getenv("ANDROID_OBJECT_FILTER");
+        if (s != null && s.length() > 0) {
+            String[] selectors = s.split("@");
+            // first selector == class name
+            if (object.getClass().getSimpleName().matches(selectors[0])) {
+                // check potential attributes
+                for (int i = 1; i < selectors.length; i++) {
+                    String[] pair = selectors[i].split("=");
+                    Class<?> klass = object.getClass();
+                    try {
+                        Method declaredMethod = null;
+                        Class<?> parent = klass;
+                        do {
+                            declaredMethod = parent.getDeclaredMethod("get" +
+                                    pair[0].substring(0, 1).toUpperCase(Locale.ROOT) +
+                                    pair[0].substring(1),
+                                    (Class[]) null);
+                        } while ((parent = klass.getSuperclass()) != null &&
+                                declaredMethod == null);
+
+                        if (declaredMethod != null) {
+                            Object value = declaredMethod
+                                    .invoke(object, (Object[])null);
+                            match |= (value != null ?
+                                    value.toString() : "null").matches(pair[1]);
+                        }
+                    } catch (NoSuchMethodException e) {
+                        e.printStackTrace();
+                    } catch (IllegalAccessException e) {
+                        e.printStackTrace();
+                    } catch (InvocationTargetException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+        return match;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public static void buildShortClassTag(Object cls, StringBuilder out) {
+        if (cls == null) {
+            out.append("null");
+        } else {
+            String simpleName = cls.getClass().getSimpleName();
+            if (simpleName == null || simpleName.isEmpty()) {
+                simpleName = cls.getClass().getName();
+                int end = simpleName.lastIndexOf('.');
+                if (end > 0) {
+                    simpleName = simpleName.substring(end+1);
+                }
+            }
+            out.append(simpleName);
+            out.append('{');
+            out.append(Integer.toHexString(System.identityHashCode(cls)));
+        }
+    }
+
+    /** @hide */
+    public static void printSizeValue(PrintWriter pw, long number) {
+        float result = number;
+        String suffix = "";
+        if (result > 900) {
+            suffix = "KB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "MB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "GB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "TB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "PB";
+            result = result / 1024;
+        }
+        String value;
+        if (result < 1) {
+            value = String.format("%.2f", result);
+        } else if (result < 10) {
+            value = String.format("%.1f", result);
+        } else if (result < 100) {
+            value = String.format("%.0f", result);
+        } else {
+            value = String.format("%.0f", result);
+        }
+        pw.print(value);
+        pw.print(suffix);
+    }
+
+    /** @hide */
+    public static String sizeValueToString(long number, StringBuilder outBuilder) {
+        if (outBuilder == null) {
+            outBuilder = new StringBuilder(32);
+        }
+        float result = number;
+        String suffix = "";
+        if (result > 900) {
+            suffix = "KB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "MB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "GB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "TB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "PB";
+            result = result / 1024;
+        }
+        String value;
+        if (result < 1) {
+            value = String.format("%.2f", result);
+        } else if (result < 10) {
+            value = String.format("%.1f", result);
+        } else if (result < 100) {
+            value = String.format("%.0f", result);
+        } else {
+            value = String.format("%.0f", result);
+        }
+        outBuilder.append(value);
+        outBuilder.append(suffix);
+        return outBuilder.toString();
+    }
+
+    /**
+     * Use prefixed constants (static final values) on given class to turn value
+     * into human-readable string.
+     *
+     * @hide
+     */
+    public static String valueToString(Class<?> clazz, String prefix, int value) {
+        for (Field field : clazz.getDeclaredFields()) {
+            final int modifiers = field.getModifiers();
+            if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+                    && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+                try {
+                    if (value == field.getInt(null)) {
+                        return constNameWithoutPrefix(prefix, field);
+                    }
+                } catch (IllegalAccessException ignored) {
+                }
+            }
+        }
+        return Integer.toString(value);
+    }
+
+    /**
+     * Use prefixed constants (static final values) on given class to turn flags
+     * into human-readable string.
+     *
+     * @hide
+     */
+    public static String flagsToString(Class<?> clazz, String prefix, int flags) {
+        final StringBuilder res = new StringBuilder();
+        boolean flagsWasZero = flags == 0;
+
+        for (Field field : clazz.getDeclaredFields()) {
+            final int modifiers = field.getModifiers();
+            if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+                    && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+                try {
+                    final int value = field.getInt(null);
+                    if (value == 0 && flagsWasZero) {
+                        return constNameWithoutPrefix(prefix, field);
+                    }
+                    if (value != 0 && (flags & value) == value) {
+                        flags &= ~value;
+                        res.append(constNameWithoutPrefix(prefix, field)).append('|');
+                    }
+                } catch (IllegalAccessException ignored) {
+                }
+            }
+        }
+        if (flags != 0 || res.length() == 0) {
+            res.append(Integer.toHexString(flags));
+        } else {
+            res.deleteCharAt(res.length() - 1);
+        }
+        return res.toString();
+    }
+
+    private static String constNameWithoutPrefix(String prefix, Field field) {
+        return field.getName().substring(prefix.length());
+    }
+
+    /**
+     * Returns method names from current stack trace, where {@link StackTraceElement#getClass}
+     * starts with the given classes name
+     *
+     * @hide
+     */
+    public static List<String> callersWithin(Class<?> cls, int offset) {
+        List<String> result = Arrays.stream(Thread.currentThread().getStackTrace())
+                .skip(offset + 3)
+                .filter(st -> st.getClassName().startsWith(cls.getName()))
+                .map(StackTraceElement::getMethodName)
+                .collect(Collectors.toList());
+        Collections.reverse(result);
+        return result;
+    }
+}
diff --git a/android/util/DisplayMetrics.java b/android/util/DisplayMetrics.java
new file mode 100644
index 0000000..9f6065e
--- /dev/null
+++ b/android/util/DisplayMetrics.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.SystemProperties;
+
+
+/**
+ * A structure describing general information about a display, such as its
+ * size, density, and font scaling.
+ * <p>To access the DisplayMetrics members, retrieve display metrics like this:</p>
+ * <pre>context.getResources().getDisplayMetrics();</pre>
+ */
+public class DisplayMetrics {
+    /**
+     * Standard quantized DPI for low-density screens.
+     */
+    public static final int DENSITY_LOW = 120;
+
+    /**
+     * Intermediate density for screens that sit between {@link #DENSITY_LOW} (120dpi) and
+     * {@link #DENSITY_MEDIUM} (160dpi). This is not a density that applications should target,
+     * instead relying on the system to scale their {@link #DENSITY_MEDIUM} assets for them.
+     */
+    public static final int DENSITY_140 = 140;
+
+    /**
+     * Standard quantized DPI for medium-density screens.
+     */
+    public static final int DENSITY_MEDIUM = 160;
+
+    /**
+     * Intermediate density for screens that sit between {@link #DENSITY_MEDIUM} (160dpi) and
+     * {@link #DENSITY_HIGH} (240dpi). This is not a density that applications should target,
+     * instead relying on the system to scale their {@link #DENSITY_HIGH} assets for them.
+     */
+    public static final int DENSITY_180 = 180;
+
+    /**
+     * Intermediate density for screens that sit between {@link #DENSITY_MEDIUM} (160dpi) and
+     * {@link #DENSITY_HIGH} (240dpi). This is not a density that applications should target,
+     * instead relying on the system to scale their {@link #DENSITY_HIGH} assets for them.
+     */
+    public static final int DENSITY_200 = 200;
+
+    /**
+     * This is a secondary density, added for some common screen configurations.
+     * It is recommended that applications not generally target this as a first
+     * class density -- that is, don't supply specific graphics for this
+     * density, instead allow the platform to scale from other densities
+     * (typically {@link #DENSITY_HIGH}) as
+     * appropriate.  In most cases (such as using bitmaps in
+     * {@link android.graphics.drawable.Drawable}) the platform
+     * can perform this scaling at load time, so the only cost is some slight
+     * startup runtime overhead.
+     *
+     * <p>This density was original introduced to correspond with a
+     * 720p TV screen: the density for 1080p televisions is
+     * {@link #DENSITY_XHIGH}, and the value here provides the same UI
+     * size for a TV running at 720p.  It has also found use in 7" tablets,
+     * when these devices have 1280x720 displays.
+     */
+    public static final int DENSITY_TV = 213;
+
+    /**
+     * Intermediate density for screens that sit between {@link #DENSITY_MEDIUM} (160dpi) and
+     * {@link #DENSITY_HIGH} (240dpi). This is not a density that applications should target,
+     * instead relying on the system to scale their {@link #DENSITY_HIGH} assets for them.
+     */
+    public static final int DENSITY_220 = 220;
+
+    /**
+     * Standard quantized DPI for high-density screens.
+     */
+    public static final int DENSITY_HIGH = 240;
+
+    /**
+     * Intermediate density for screens that sit between {@link #DENSITY_HIGH} (240dpi) and
+     * {@link #DENSITY_XHIGH} (320dpi). This is not a density that applications should target,
+     * instead relying on the system to scale their {@link #DENSITY_XHIGH} assets for them.
+     */
+    public static final int DENSITY_260 = 260;
+
+    /**
+     * Intermediate density for screens that sit between {@link #DENSITY_HIGH} (240dpi) and
+     * {@link #DENSITY_XHIGH} (320dpi). This is not a density that applications should target,
+     * instead relying on the system to scale their {@link #DENSITY_XHIGH} assets for them.
+     */
+    public static final int DENSITY_280 = 280;
+
+    /**
+     * Intermediate density for screens that sit between {@link #DENSITY_HIGH} (240dpi) and
+     * {@link #DENSITY_XHIGH} (320dpi). This is not a density that applications should target,
+     * instead relying on the system to scale their {@link #DENSITY_XHIGH} assets for them.
+     */
+    public static final int DENSITY_300 = 300;
+
+    /**
+     * Standard quantized DPI for extra-high-density screens.
+     */
+    public static final int DENSITY_XHIGH = 320;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+     */
+    public static final int DENSITY_340 = 340;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+     */
+    public static final int DENSITY_360 = 360;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+     */
+    public static final int DENSITY_400 = 400;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+     */
+    public static final int DENSITY_420 = 420;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+     */
+    public static final int DENSITY_440 = 440;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+     */
+    public static final int DENSITY_450 = 450;
+
+    /**
+     * Standard quantized DPI for extra-extra-high-density screens.
+     */
+    public static final int DENSITY_XXHIGH = 480;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XXHIGH} (480 dpi) and {@link #DENSITY_XXXHIGH} (640 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXXHIGH} assets for them.
+     */
+    public static final int DENSITY_560 = 560;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XXHIGH} (480 dpi) and {@link #DENSITY_XXXHIGH} (640 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXXHIGH} assets for them.
+     */
+    public static final int DENSITY_600 = 600;
+
+    /**
+     * Standard quantized DPI for extra-extra-extra-high-density screens.  Applications
+     * should not generally worry about this density; relying on XHIGH graphics
+     * being scaled up to it should be sufficient for almost all cases.  A typical
+     * use of this density would be 4K television screens -- 3840x2160, which
+     * is 2x a traditional HD 1920x1080 screen which runs at DENSITY_XHIGH.
+     */
+    public static final int DENSITY_XXXHIGH = 640;
+
+    /**
+     * The reference density used throughout the system.
+     */
+    public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
+
+    /**
+     * Scaling factor to convert a density in DPI units to the density scale.
+     * @hide
+     */
+    public static final float DENSITY_DEFAULT_SCALE = 1.0f / DENSITY_DEFAULT;
+
+    /**
+     * The device's current density.
+     * <p>
+     * This value reflects any changes made to the device density. To obtain
+     * the device's stable density, use {@link #DENSITY_DEVICE_STABLE}.
+     *
+     * @hide This value should not be used.
+     * @deprecated Use {@link #DENSITY_DEVICE_STABLE} to obtain the stable
+     *             device density or {@link #densityDpi} to obtain the current
+     *             density for a specific display.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public static int DENSITY_DEVICE = getDeviceDensity();
+
+    /**
+     * The device's stable density.
+     * <p>
+     * This value is constant at run time and may not reflect the current
+     * display density. To obtain the current density for a specific display,
+     * use {@link #densityDpi}.
+     */
+    public static final int DENSITY_DEVICE_STABLE = getDeviceDensity();
+
+    /**
+     * The absolute width of the available display size in pixels.
+     */
+    public int widthPixels;
+    /**
+     * The absolute height of the available display size in pixels.
+     */
+    public int heightPixels;
+    /**
+     * The logical density of the display.  This is a scaling factor for the
+     * Density Independent Pixel unit, where one DIP is one pixel on an
+     * approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen), 
+     * providing the baseline of the system's display. Thus on a 160dpi screen 
+     * this density value will be 1; on a 120 dpi screen it would be .75; etc.
+     *  
+     * <p>This value does not exactly follow the real screen size (as given by 
+     * {@link #xdpi} and {@link #ydpi}), but rather is used to scale the size of
+     * the overall UI in steps based on gross changes in the display dpi.  For 
+     * example, a 240x320 screen will have a density of 1 even if its width is 
+     * 1.8", 1.3", etc. However, if the screen resolution is increased to 
+     * 320x480 but the screen size remained 1.5"x2" then the density would be 
+     * increased (probably to 1.5).
+     *
+     * @see #DENSITY_DEFAULT
+     */
+    public float density;
+    /**
+     * The screen density expressed as dots-per-inch.  May be either
+     * {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}.
+     */
+    public int densityDpi;
+    /**
+     * A scaling factor for fonts displayed on the display.  This is the same
+     * as {@link #density}, except that it may be adjusted in smaller
+     * increments at runtime based on a user preference for the font size.
+     */
+    public float scaledDensity;
+    /**
+     * The exact physical pixels per inch of the screen in the X dimension.
+     */
+    public float xdpi;
+    /**
+     * The exact physical pixels per inch of the screen in the Y dimension.
+     */
+    public float ydpi;
+
+    /**
+     * The reported display width prior to any compatibility mode scaling
+     * being applied.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int noncompatWidthPixels;
+    /**
+     * The reported display height prior to any compatibility mode scaling
+     * being applied.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int noncompatHeightPixels;
+    /**
+     * The reported display density prior to any compatibility mode scaling
+     * being applied.
+     * @hide
+     */
+    public float noncompatDensity;
+    /**
+     * The reported display density prior to any compatibility mode scaling
+     * being applied.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int noncompatDensityDpi;
+    /**
+     * The reported scaled density prior to any compatibility mode scaling
+     * being applied.
+     * @hide
+     */
+    public float noncompatScaledDensity;
+    /**
+     * The reported display xdpi prior to any compatibility mode scaling
+     * being applied.
+     * @hide
+     */
+    public float noncompatXdpi;
+    /**
+     * The reported display ydpi prior to any compatibility mode scaling
+     * being applied.
+     * @hide
+     */
+    public float noncompatYdpi;
+
+    public DisplayMetrics() {
+    }
+    
+    public void setTo(DisplayMetrics o) {
+        if (this == o) {
+            return;
+        }
+
+        widthPixels = o.widthPixels;
+        heightPixels = o.heightPixels;
+        density = o.density;
+        densityDpi = o.densityDpi;
+        scaledDensity = o.scaledDensity;
+        xdpi = o.xdpi;
+        ydpi = o.ydpi;
+        noncompatWidthPixels = o.noncompatWidthPixels;
+        noncompatHeightPixels = o.noncompatHeightPixels;
+        noncompatDensity = o.noncompatDensity;
+        noncompatDensityDpi = o.noncompatDensityDpi;
+        noncompatScaledDensity = o.noncompatScaledDensity;
+        noncompatXdpi = o.noncompatXdpi;
+        noncompatYdpi = o.noncompatYdpi;
+    }
+    
+    public void setToDefaults() {
+        widthPixels = 0;
+        heightPixels = 0;
+        density =  DENSITY_DEVICE / (float) DENSITY_DEFAULT;
+        densityDpi =  DENSITY_DEVICE;
+        scaledDensity = density;
+        xdpi = DENSITY_DEVICE;
+        ydpi = DENSITY_DEVICE;
+        noncompatWidthPixels = widthPixels;
+        noncompatHeightPixels = heightPixels;
+        noncompatDensity = density;
+        noncompatDensityDpi = densityDpi;
+        noncompatScaledDensity = scaledDensity;
+        noncompatXdpi = xdpi;
+        noncompatYdpi = ydpi;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof DisplayMetrics && equals((DisplayMetrics)o);
+    }
+
+    /**
+     * Returns true if these display metrics equal the other display metrics.
+     *
+     * @param other The display metrics with which to compare.
+     * @return True if the display metrics are equal.
+     */
+    public boolean equals(DisplayMetrics other) {
+        return equalsPhysical(other)
+                && scaledDensity == other.scaledDensity
+                && noncompatScaledDensity == other.noncompatScaledDensity;
+    }
+
+    /**
+     * Returns true if the physical aspects of the two display metrics
+     * are equal.  This ignores the scaled density, which is a logical
+     * attribute based on the current desired font size.
+     *
+     * @param other The display metrics with which to compare.
+     * @return True if the display metrics are equal.
+     * @hide
+     */
+    public boolean equalsPhysical(DisplayMetrics other) {
+        return other != null
+                && widthPixels == other.widthPixels
+                && heightPixels == other.heightPixels
+                && density == other.density
+                && densityDpi == other.densityDpi
+                && xdpi == other.xdpi
+                && ydpi == other.ydpi
+                && noncompatWidthPixels == other.noncompatWidthPixels
+                && noncompatHeightPixels == other.noncompatHeightPixels
+                && noncompatDensity == other.noncompatDensity
+                && noncompatDensityDpi == other.noncompatDensityDpi
+                && noncompatXdpi == other.noncompatXdpi
+                && noncompatYdpi == other.noncompatYdpi;
+    }
+
+    @Override
+    public int hashCode() {
+        return widthPixels * heightPixels * densityDpi;
+    }
+
+    @Override
+    public String toString() {
+        return "DisplayMetrics{density=" + density + ", width=" + widthPixels +
+            ", height=" + heightPixels + ", scaledDensity=" + scaledDensity +
+            ", xdpi=" + xdpi + ", ydpi=" + ydpi + "}";
+    }
+
+    private static int getDeviceDensity() {
+        // qemu.sf.lcd_density can be used to override ro.sf.lcd_density
+        // when running in the emulator, allowing for dynamic configurations.
+        // The reason for this is that ro.sf.lcd_density is write-once and is
+        // set by the init process when it parses build.prop before anything else.
+        return SystemProperties.getInt("qemu.sf.lcd_density",
+                SystemProperties.getInt("ro.sf.lcd_density", DENSITY_DEFAULT));
+    }
+}
diff --git a/android/util/EventLog.java b/android/util/EventLog.java
new file mode 100644
index 0000000..ead4e46
--- /dev/null
+++ b/android/util/EventLog.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Access to the system diagnostic event record.  System diagnostic events are
+ * used to record certain system-level events (such as garbage collection,
+ * activity manager state, system watchdogs, and other low level activity),
+ * which may be automatically collected and analyzed during system development.
+ *
+ * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
+ * These diagnostic events are for system integrators, not application authors.
+ *
+ * <p>Events use integer tag codes corresponding to /system/etc/event-log-tags.
+ * They carry a payload of one or more int, long, or String values.  The
+ * event-log-tags file defines the payload contents for each type code.
+ */
+public class EventLog {
+    /** @hide */ public EventLog() {}
+
+    private static final String TAG = "EventLog";
+
+    private static final String TAGS_FILE = "/system/etc/event-log-tags";
+    private static final String COMMENT_PATTERN = "^\\s*(#.*)?$";
+    private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$";
+    private static HashMap<String, Integer> sTagCodes = null;
+    private static HashMap<Integer, String> sTagNames = null;
+
+    /** A previously logged event read from the logs. Instances are thread safe. */
+    public static final class Event {
+        private final ByteBuffer mBuffer;
+        private Exception mLastWtf;
+
+        // Layout of event log entry received from Android logger.
+        //  see system/core/liblog/include/log/log_read.h
+        private static final int LENGTH_OFFSET = 0;
+        private static final int HEADER_SIZE_OFFSET = 2;
+        private static final int PROCESS_OFFSET = 4;
+        private static final int THREAD_OFFSET = 8;
+        private static final int SECONDS_OFFSET = 12;
+        private static final int NANOSECONDS_OFFSET = 16;
+        private static final int UID_OFFSET = 24;
+
+        // Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET
+        private static final int V1_PAYLOAD_START = 20;
+        private static final int TAG_LENGTH = 4;
+
+        // Value types
+        private static final byte INT_TYPE    = 0;
+        private static final byte LONG_TYPE   = 1;
+        private static final byte STRING_TYPE = 2;
+        private static final byte LIST_TYPE   = 3;
+        private static final byte FLOAT_TYPE = 4;
+
+        /** @param data containing event, read from the system */
+        @UnsupportedAppUsage
+        /*package*/ Event(byte[] data) {
+            mBuffer = ByteBuffer.wrap(data);
+            mBuffer.order(ByteOrder.nativeOrder());
+        }
+
+        /** @return the process ID which wrote the log entry */
+        public int getProcessId() {
+            return mBuffer.getInt(PROCESS_OFFSET);
+        }
+
+        /**
+         * @return the UID which wrote the log entry
+         * @hide
+         */
+        @SystemApi
+        public int getUid() {
+            try {
+                return mBuffer.getInt(UID_OFFSET);
+            } catch (IndexOutOfBoundsException e) {
+                // buffer won't contain the UID if the caller doesn't have permission.
+                return -1;
+            }
+        }
+
+        /** @return the thread ID which wrote the log entry */
+        public int getThreadId() {
+            return mBuffer.getInt(THREAD_OFFSET);
+        }
+
+        /** @return the wall clock time when the entry was written */
+        public long getTimeNanos() {
+            return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l
+                    + mBuffer.getInt(NANOSECONDS_OFFSET);
+        }
+
+        /** @return the type tag code of the entry */
+        public int getTag() {
+            return mBuffer.getInt(getHeaderSize());
+        }
+
+        private int getHeaderSize() {
+            int length = mBuffer.getShort(HEADER_SIZE_OFFSET);
+            if (length != 0) {
+                return length;
+            }
+            return V1_PAYLOAD_START;
+        }
+        /** @return one of Integer, Long, Float, String, null, or Object[] of same. */
+        public synchronized Object getData() {
+            try {
+                int offset = getHeaderSize();
+                mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
+                if ((offset + TAG_LENGTH) >= mBuffer.limit()) {
+                    // no payload
+                    return null;
+                }
+                mBuffer.position(offset + TAG_LENGTH); // Just after the tag.
+                return decodeObject();
+            } catch (IllegalArgumentException e) {
+                Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
+                mLastWtf = e;
+                return null;
+            } catch (BufferUnderflowException e) {
+                Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e);
+                mLastWtf = e;
+                return null;
+            }
+        }
+
+        /**
+         * Construct a new EventLog object from the current object, copying all log metadata
+         * but replacing the actual payload with the content provided.
+         * @hide
+         */
+        public Event withNewData(@Nullable Object object) {
+            byte[] payload = encodeObject(object);
+            if (payload.length > 65535 - TAG_LENGTH) {
+                throw new IllegalArgumentException("Payload too long");
+            }
+            int headerLength = getHeaderSize();
+            byte[] newBytes = new byte[headerLength + TAG_LENGTH + payload.length];
+            // Copy header (including the 4 bytes of tag integer at the beginning of payload)
+            System.arraycopy(mBuffer.array(), 0, newBytes, 0, headerLength + TAG_LENGTH);
+            // Fill in encoded objects
+            System.arraycopy(payload, 0, newBytes, headerLength + TAG_LENGTH, payload.length);
+            Event result = new Event(newBytes);
+            // Patch payload length in header
+            result.mBuffer.putShort(LENGTH_OFFSET, (short) (payload.length + TAG_LENGTH));
+            return result;
+        }
+
+        /** @return the loggable item at the current position in mBuffer. */
+        private Object decodeObject() {
+            byte type = mBuffer.get();
+            switch (type) {
+            case INT_TYPE:
+                return mBuffer.getInt();
+
+            case LONG_TYPE:
+                return mBuffer.getLong();
+
+            case FLOAT_TYPE:
+                return mBuffer.getFloat();
+
+            case STRING_TYPE:
+                try {
+                    int length = mBuffer.getInt();
+                    int start = mBuffer.position();
+                    mBuffer.position(start + length);
+                    return new String(mBuffer.array(), start, length, "UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    Log.wtf(TAG, "UTF-8 is not supported", e);
+                    mLastWtf = e;
+                    return null;
+                }
+
+            case LIST_TYPE:
+                int length = mBuffer.get();
+                if (length < 0) length += 256;  // treat as signed byte
+                Object[] array = new Object[length];
+                for (int i = 0; i < length; ++i) array[i] = decodeObject();
+                return array;
+
+            default:
+                throw new IllegalArgumentException("Unknown entry type: " + type);
+            }
+        }
+
+        private static @NonNull byte[] encodeObject(@Nullable Object object) {
+            if (object == null) {
+                return new byte[0];
+            }
+            if (object instanceof Integer) {
+                return ByteBuffer.allocate(1 + 4)
+                        .order(ByteOrder.nativeOrder())
+                        .put(INT_TYPE)
+                        .putInt((Integer) object)
+                        .array();
+            } else if (object instanceof Long) {
+                return ByteBuffer.allocate(1 + 8)
+                        .order(ByteOrder.nativeOrder())
+                        .put(LONG_TYPE)
+                        .putLong((Long) object)
+                        .array();
+            } else if (object instanceof Float) {
+                return ByteBuffer.allocate(1 + 4)
+                        .order(ByteOrder.nativeOrder())
+                        .put(FLOAT_TYPE)
+                        .putFloat((Float) object)
+                        .array();
+            } else if (object instanceof String) {
+                String string = (String) object;
+                byte[] bytes;
+                try {
+                    bytes = string.getBytes("UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    bytes = new byte[0];
+                }
+                return ByteBuffer.allocate(1 + 4 + bytes.length)
+                         .order(ByteOrder.nativeOrder())
+                         .put(STRING_TYPE)
+                         .putInt(bytes.length)
+                         .put(bytes)
+                         .array();
+            } else if (object instanceof Object[]) {
+                Object[] objects = (Object[]) object;
+                if (objects.length > 255) {
+                    throw new IllegalArgumentException("Object array too long");
+                }
+                byte[][] bytes = new byte[objects.length][];
+                int totalLength = 0;
+                for (int i = 0; i < objects.length; i++) {
+                    bytes[i] = encodeObject(objects[i]);
+                    totalLength += bytes[i].length;
+                }
+                ByteBuffer buffer = ByteBuffer.allocate(1 + 1 + totalLength)
+                        .order(ByteOrder.nativeOrder())
+                        .put(LIST_TYPE)
+                        .put((byte) objects.length);
+                for (int i = 0; i < objects.length; i++) {
+                    buffer.put(bytes[i]);
+                }
+                return buffer.array();
+            } else {
+                throw new IllegalArgumentException("Unknown object type " + object);
+            }
+        }
+
+        /** @hide */
+        public static Event fromBytes(byte[] data) {
+            return new Event(data);
+        }
+
+        /** @hide */
+        public byte[] getBytes() {
+            byte[] bytes = mBuffer.array();
+            return Arrays.copyOf(bytes, bytes.length);
+        }
+
+        /**
+         * Retreive the last WTF error generated by this object.
+         * @hide
+         */
+        //VisibleForTesting
+        public Exception getLastError() {
+            return mLastWtf;
+        }
+
+        /**
+         * Clear the error state for this object.
+         * @hide
+         */
+        //VisibleForTesting
+        public void clearError() {
+            mLastWtf = null;
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean equals(Object o) {
+            // Not using ByteBuffer.equals since it takes buffer position into account and we
+            // always use absolute positions here.
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Event other = (Event) o;
+            return Arrays.equals(mBuffer.array(), other.mBuffer.array());
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public int hashCode() {
+            // Not using ByteBuffer.hashCode since it takes buffer position into account and we
+            // always use absolute positions here.
+            return Arrays.hashCode(mBuffer.array());
+        }
+    }
+
+    // We assume that the native methods deal with any concurrency issues.
+
+    /**
+     * Record an event log message.
+     * @param tag The event type tag code
+     * @param value A value to log
+     * @return The number of bytes written
+     */
+    public static native int writeEvent(int tag, int value);
+
+    /**
+     * Record an event log message.
+     * @param tag The event type tag code
+     * @param value A value to log
+     * @return The number of bytes written
+     */
+    public static native int writeEvent(int tag, long value);
+
+    /**
+     * Record an event log message.
+     * @param tag The event type tag code
+     * @param value A value to log
+     * @return The number of bytes written
+     */
+    public static native int writeEvent(int tag, float value);
+
+    /**
+     * Record an event log message.
+     * @param tag The event type tag code
+     * @param str A value to log
+     * @return The number of bytes written
+     */
+    public static native int writeEvent(int tag, String str);
+
+    /**
+     * Record an event log message.
+     * @param tag The event type tag code
+     * @param list A list of values to log
+     * @return The number of bytes written
+     */
+    public static native int writeEvent(int tag, Object... list);
+
+    /**
+     * Read events from the log, filtered by type.
+     * @param tags to search for
+     * @param output container to add events into
+     * @throws IOException if something goes wrong reading events
+     */
+    public static native void readEvents(int[] tags, Collection<Event> output)
+            throws IOException;
+
+    /**
+     * Read events from the log, filtered by type, blocking until logs are about to be overwritten.
+     * @param tags to search for
+     * @param timestamp timestamp allow logs before this time to be overwritten.
+     * @param output container to add events into
+     * @throws IOException if something goes wrong reading events
+     * @hide
+     */
+    @SystemApi
+    public static native void readEventsOnWrapping(int[] tags, long timestamp,
+            Collection<Event> output)
+            throws IOException;
+
+    /**
+     * Get the name associated with an event type tag code.
+     * @param tag code to look up
+     * @return the name of the tag, or null if no tag has that number
+     */
+    public static String getTagName(int tag) {
+        readTagsFile();
+        return sTagNames.get(tag);
+    }
+
+    /**
+     * Get the event type tag code associated with an event name.
+     * @param name of event to look up
+     * @return the tag code, or -1 if no tag has that name
+     */
+    public static int getTagCode(String name) {
+        readTagsFile();
+        Integer code = sTagCodes.get(name);
+        return code != null ? code : -1;
+    }
+
+    /**
+     * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
+     */
+    private static synchronized void readTagsFile() {
+        if (sTagCodes != null && sTagNames != null) return;
+
+        sTagCodes = new HashMap<String, Integer>();
+        sTagNames = new HashMap<Integer, String>();
+
+        Pattern comment = Pattern.compile(COMMENT_PATTERN);
+        Pattern tag = Pattern.compile(TAG_PATTERN);
+        BufferedReader reader = null;
+        String line;
+
+        try {
+            reader = new BufferedReader(new FileReader(TAGS_FILE), 256);
+            while ((line = reader.readLine()) != null) {
+                if (comment.matcher(line).matches()) continue;
+
+                Matcher m = tag.matcher(line);
+                if (!m.matches()) {
+                    Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line);
+                    continue;
+                }
+
+                try {
+                    int num = Integer.parseInt(m.group(1));
+                    String name = m.group(2);
+                    sTagCodes.put(name, num);
+                    sTagNames.put(num, name);
+                } catch (NumberFormatException e) {
+                    Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
+                }
+            }
+        } catch (IOException e) {
+            Log.wtf(TAG, "Error reading " + TAGS_FILE, e);
+            // Leave the maps existing but unpopulated
+        } finally {
+            try { if (reader != null) reader.close(); } catch (IOException e) {}
+        }
+    }
+}
diff --git a/android/util/EventLogTags.java b/android/util/EventLogTags.java
new file mode 100644
index 0000000..f4ce4fd
--- /dev/null
+++ b/android/util/EventLogTags.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 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;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+/**
+ * @deprecated This class is no longer functional.
+ * Use {@link android.util.EventLog} instead.
+ */
+@Deprecated
+public class EventLogTags {
+    public static class Description {
+        public final int mTag;
+        public final String mName;
+
+        Description(int tag, String name) {
+            mTag = tag;
+            mName = name;
+        }
+    }
+
+    public EventLogTags() throws IOException {}
+
+    public EventLogTags(BufferedReader input) throws IOException {}
+
+    public Description get(String name) { return null; }
+
+    public Description get(int tag) { return null; }
+}
diff --git a/android/util/ExceptionUtils.java b/android/util/ExceptionUtils.java
new file mode 100644
index 0000000..1a397b3
--- /dev/null
+++ b/android/util/ExceptionUtils.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelableException;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+
+/**
+ * Utility methods for proxying richer exceptions across Binder calls.
+ *
+ * @hide
+ */
+public class ExceptionUtils {
+    public static RuntimeException wrap(IOException e) {
+        throw new ParcelableException(e);
+    }
+
+    public static void maybeUnwrapIOException(RuntimeException e) throws IOException {
+        if (e instanceof ParcelableException) {
+            ((ParcelableException) e).maybeRethrow(IOException.class);
+        }
+    }
+
+    public static String getCompleteMessage(String msg, Throwable t) {
+        final StringBuilder builder = new StringBuilder();
+        if (msg != null) {
+            builder.append(msg).append(": ");
+        }
+        builder.append(t.getMessage());
+        while ((t = t.getCause()) != null) {
+            builder.append(": ").append(t.getMessage());
+        }
+        return builder.toString();
+    }
+
+    public static String getCompleteMessage(Throwable t) {
+        return getCompleteMessage(null, t);
+    }
+
+    public static <E extends Throwable> void propagateIfInstanceOf(
+            @Nullable Throwable t, Class<E> c) throws E {
+        if (t != null && c.isInstance(t)) {
+            throw c.cast(t);
+        }
+    }
+
+    /**
+     * @param <E> a checked exception that is ok to throw without wrapping
+     */
+    public static <E extends Exception> RuntimeException propagate(@NonNull Throwable t, Class<E> c)
+            throws E {
+        propagateIfInstanceOf(t, c);
+        return propagate(t);
+    }
+
+    public static RuntimeException propagate(@NonNull Throwable t) {
+        Preconditions.checkNotNull(t);
+        propagateIfInstanceOf(t, Error.class);
+        propagateIfInstanceOf(t, RuntimeException.class);
+        throw new RuntimeException(t);
+    }
+
+    /**
+     * Gets the root {@link Throwable#getCause() cause} of {@code t}
+     */
+    public static @NonNull Throwable getRootCause(@NonNull Throwable t) {
+        while (t.getCause() != null) t = t.getCause();
+        return t;
+    }
+
+    /**
+     * Appends {@code cause} at the end of the causal chain of {@code t}
+     *
+     * @return {@code t} for convenience
+     */
+    public static @NonNull Throwable appendCause(@NonNull Throwable t, @Nullable Throwable cause) {
+        if (cause != null) {
+            getRootCause(t).initCause(cause);
+        }
+        return t;
+    }
+}
\ No newline at end of file
diff --git a/android/util/FastImmutableArraySet.java b/android/util/FastImmutableArraySet.java
new file mode 100644
index 0000000..4175c60
--- /dev/null
+++ b/android/util/FastImmutableArraySet.java
@@ -0,0 +1,78 @@
+/*
+ * 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 android.util;
+
+import java.util.AbstractSet;
+import java.util.Iterator;
+
+/**
+ * A fast immutable set wrapper for an array that is optimized for non-concurrent iteration.
+ * The same iterator instance is reused each time to avoid creating lots of garbage.
+ * Iterating over an array in this fashion is 2.5x faster than iterating over a {@link HashSet}
+ * so it is worth copying the contents of the set to an array when iterating over it
+ * hundreds of times.
+ * @hide
+ */
+public final class FastImmutableArraySet<T> extends AbstractSet<T> {
+    FastIterator<T> mIterator;
+    T[] mContents;
+
+    public FastImmutableArraySet(T[] contents) {
+        mContents = contents;
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        FastIterator<T> it = mIterator;
+        if (it == null) {
+            it = new FastIterator<T>(mContents);
+            mIterator = it;
+        } else {
+            it.mIndex = 0;
+        }
+        return it;
+    }
+
+    @Override
+    public int size() {
+        return mContents.length;
+    }
+
+    private static final class FastIterator<T> implements Iterator<T> {
+        private final T[] mContents;
+        int mIndex;
+
+        public FastIterator(T[] contents) {
+            mContents = contents;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return mIndex != mContents.length;
+        }
+
+        @Override
+        public T next() {
+            return mContents[mIndex++];
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/android/util/FeatureFlagUtils.java b/android/util/FeatureFlagUtils.java
new file mode 100644
index 0000000..cd20b35
--- /dev/null
+++ b/android/util/FeatureFlagUtils.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Util class to get feature flag information.
+ *
+ * @hide
+ */
+@TestApi
+public class FeatureFlagUtils {
+
+    public static final String FFLAG_PREFIX = "sys.fflag.";
+    public static final String FFLAG_OVERRIDE_PREFIX = FFLAG_PREFIX + "override.";
+    public static final String PERSIST_PREFIX = "persist." + FFLAG_OVERRIDE_PREFIX;
+    public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer";
+    public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
+    public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
+    public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
+    public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2";
+    public static final String SETTINGS_FUSE_FLAG = "settings_fuse";
+    /** @hide */
+    public static final String SETTINGS_DO_NOT_RESTORE_PRESERVED =
+            "settings_do_not_restore_preserved";
+    /** @hide */
+    public static final String SETTINGS_SCHEDULES_FLAG = "settings_schedules";
+
+    private static final Map<String, String> DEFAULT_FLAGS;
+
+    static {
+        DEFAULT_FLAGS = new HashMap<>();
+        DEFAULT_FLAGS.put("settings_audio_switcher", "true");
+        DEFAULT_FLAGS.put("settings_systemui_theme", "true");
+        DEFAULT_FLAGS.put(SETTINGS_FUSE_FLAG, "true");
+        DEFAULT_FLAGS.put(DYNAMIC_SYSTEM, "false");
+        DEFAULT_FLAGS.put(SEAMLESS_TRANSFER, "false");
+        DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
+        DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
+        DEFAULT_FLAGS.put("settings_wifi_details_datausage_header", "false");
+        DEFAULT_FLAGS.put("settings_skip_direction_mutable", "true");
+        DEFAULT_FLAGS.put(SETTINGS_WIFITRACKER2, "true");
+        DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false");
+        DEFAULT_FLAGS.put("settings_conditionals", "false");
+        // This flags guards a feature introduced in R and will be removed in the next release
+        // (b/148367230).
+        DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "true");
+
+        DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
+        DEFAULT_FLAGS.put(SETTINGS_SCHEDULES_FLAG, "false");
+        DEFAULT_FLAGS.put("settings_contextual_home2", "true");
+    }
+
+    /**
+     * Whether or not a flag is enabled.
+     *
+     * @param feature the flag name
+     * @return true if the flag is enabled (either by default in system, or override by user)
+     */
+    public static boolean isEnabled(Context context, String feature) {
+        // Override precedence:
+        // Settings.Global -> sys.fflag.override.* -> static list
+
+        // Step 1: check if feature flag is set in Settings.Global.
+        String value;
+        if (context != null) {
+            value = Settings.Global.getString(context.getContentResolver(), feature);
+            if (!TextUtils.isEmpty(value)) {
+                return Boolean.parseBoolean(value);
+            }
+        }
+
+        // Step 2: check if feature flag has any override. Flag name: sys.fflag.override.<feature>
+        value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature);
+        if (!TextUtils.isEmpty(value)) {
+            return Boolean.parseBoolean(value);
+        }
+        // Step 3: check if feature flag has any default value.
+        value = getAllFeatureFlags().get(feature);
+        return Boolean.parseBoolean(value);
+    }
+
+    /**
+     * Override feature flag to new state.
+     */
+    public static void setEnabled(Context context, String feature, boolean enabled) {
+        SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
+    }
+
+    /**
+     * Returns all feature flags in their raw form.
+     */
+    public static Map<String, String> getAllFeatureFlags() {
+        return DEFAULT_FLAGS;
+    }
+}
diff --git a/android/util/FloatMath.java b/android/util/FloatMath.java
new file mode 100644
index 0000000..bb7d15f
--- /dev/null
+++ b/android/util/FloatMath.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2007 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;
+
+/**
+ * Math routines similar to those found in {@link java.lang.Math}.
+ *
+ * <p>Historically these methods were faster than the equivalent double-based
+ * {@link java.lang.Math} methods. On versions of Android with a JIT they
+ * became slower and have since been re-implemented to wrap calls to
+ * {@link java.lang.Math}. {@link java.lang.Math} should be used in
+ * preference.
+ *
+ * <p>All methods were removed from the public API in version 23.
+ *
+ * @deprecated Use {@link java.lang.Math} instead.
+ */
+@Deprecated
+public class FloatMath {
+
+    /** Prevents instantiation. */
+    private FloatMath() {}
+
+    /**
+     * Returns the float conversion of the most positive (i.e. closest to
+     * positive infinity) integer value which is less than the argument.
+     *
+     * @param value to be converted
+     * @return the floor of value
+     * @removed
+     */
+    public static float floor(float value) {
+        return (float) Math.floor(value);
+    }
+
+    /**
+     * Returns the float conversion of the most negative (i.e. closest to
+     * negative infinity) integer value which is greater than the argument.
+     *
+     * @param value to be converted
+     * @return the ceiling of value
+     * @removed
+     */
+    public static float ceil(float value) {
+        return (float) Math.ceil(value);
+    }
+
+    /**
+     * Returns the closest float approximation of the sine of the argument.
+     *
+     * @param angle to compute the cosine of, in radians
+     * @return the sine of angle
+     * @removed
+     */
+    public static float sin(float angle) {
+        return (float) Math.sin(angle);
+    }
+
+    /**
+     * Returns the closest float approximation of the cosine of the argument.
+     *
+     * @param angle to compute the cosine of, in radians
+     * @return the cosine of angle
+     * @removed
+     */
+    public static float cos(float angle) {
+        return (float) Math.cos(angle);
+    }
+
+    /**
+     * Returns the closest float approximation of the square root of the
+     * argument.
+     *
+     * @param value to compute sqrt of
+     * @return the square root of value
+     * @removed
+     */
+    public static float sqrt(float value) {
+        return (float) Math.sqrt(value);
+    }
+
+    /**
+     * Returns the closest float approximation of the raising "e" to the power
+     * of the argument.
+     *
+     * @param value to compute the exponential of
+     * @return the exponential of value
+     * @removed
+     */
+    public static float exp(float value) {
+        return (float) Math.exp(value);
+    }
+
+    /**
+     * Returns the closest float approximation of the result of raising {@code
+     * x} to the power of {@code y}.
+     *
+     * @param x the base of the operation.
+     * @param y the exponent of the operation.
+     * @return {@code x} to the power of {@code y}.
+     * @removed
+     */
+    public static float pow(float x, float y) {
+        return (float) Math.pow(x, y);
+    }
+
+    /**
+     * Returns {@code sqrt(}<i>{@code x}</i><sup>{@code 2}</sup>{@code +} <i>
+     * {@code y}</i><sup>{@code 2}</sup>{@code )}.
+     *
+     * @param x a float number
+     * @param y a float number
+     * @return the hypotenuse
+     * @removed
+     */
+    public static float hypot(float x, float y) {
+        return (float) Math.hypot(x, y);
+    }
+}
diff --git a/android/util/FloatProperty.java b/android/util/FloatProperty.java
new file mode 100644
index 0000000..4aac196
--- /dev/null
+++ b/android/util/FloatProperty.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.util;
+
+/**
+ * An implementation of {@link android.util.Property} to be used specifically with fields of type
+ * <code>float</code>. This type-specific subclass enables performance benefit by allowing
+ * calls to a {@link #setValue(Object, float) setValue()} function that takes the primitive
+ * <code>float</code> type and avoids autoboxing and other overhead associated with the
+ * <code>Float</code> class.
+ *
+ * @param <T> The class on which the Property is declared.
+ */
+public abstract class FloatProperty<T> extends Property<T, Float> {
+
+    public FloatProperty(String name) {
+        super(Float.class, name);
+    }
+
+    /**
+     * A type-specific variant of {@link #set(Object, Float)} that is faster when dealing
+     * with fields of type <code>float</code>.
+     */
+    public abstract void setValue(T object, float value);
+
+    @Override
+    final public void set(T object, Float value) {
+        setValue(object, value);
+    }
+
+}
\ No newline at end of file
diff --git a/android/util/Half.java b/android/util/Half.java
new file mode 100644
index 0000000..fe536a6
--- /dev/null
+++ b/android/util/Half.java
@@ -0,0 +1,904 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.annotation.HalfFloat;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import libcore.util.FP16;
+
+/**
+ * <p>The {@code Half} class is a wrapper and a utility class to manipulate half-precision 16-bit
+ * <a href="https://en.wikipedia.org/wiki/Half-precision_floating-point_format">IEEE 754</a>
+ * floating point data types (also called fp16 or binary16). A half-precision float can be
+ * created from or converted to single-precision floats, and is stored in a short data type.
+ * To distinguish short values holding half-precision floats from regular short values,
+ * it is recommended to use the <code>@HalfFloat</code> annotation.</p>
+ *
+ * <p>The IEEE 754 standard specifies an fp16 as having the following format:</p>
+ * <ul>
+ * <li>Sign bit: 1 bit</li>
+ * <li>Exponent width: 5 bits</li>
+ * <li>Significand: 10 bits</li>
+ * </ul>
+ *
+ * <p>The format is laid out as follows:</p>
+ * <pre>
+ * 1   11111   1111111111
+ * ^   --^--   -----^----
+ * sign  |          |_______ significand
+ *       |
+ *       -- exponent
+ * </pre>
+ *
+ * <p>Half-precision floating points can be useful to save memory and/or
+ * bandwidth at the expense of range and precision when compared to single-precision
+ * floating points (fp32).</p>
+ * <p>To help you decide whether fp16 is the right storage type for you need, please
+ * refer to the table below that shows the available precision throughout the range of
+ * possible values. The <em>precision</em> column indicates the step size between two
+ * consecutive numbers in a specific part of the range.</p>
+ *
+ * <table summary="Precision of fp16 across the range">
+ *     <tr><th>Range start</th><th>Precision</th></tr>
+ *     <tr><td>0</td><td>1 &frasl; 16,777,216</td></tr>
+ *     <tr><td>1 &frasl; 16,384</td><td>1 &frasl; 16,777,216</td></tr>
+ *     <tr><td>1 &frasl; 8,192</td><td>1 &frasl; 8,388,608</td></tr>
+ *     <tr><td>1 &frasl; 4,096</td><td>1 &frasl; 4,194,304</td></tr>
+ *     <tr><td>1 &frasl; 2,048</td><td>1 &frasl; 2,097,152</td></tr>
+ *     <tr><td>1 &frasl; 1,024</td><td>1 &frasl; 1,048,576</td></tr>
+ *     <tr><td>1 &frasl; 512</td><td>1 &frasl; 524,288</td></tr>
+ *     <tr><td>1 &frasl; 256</td><td>1 &frasl; 262,144</td></tr>
+ *     <tr><td>1 &frasl; 128</td><td>1 &frasl; 131,072</td></tr>
+ *     <tr><td>1 &frasl; 64</td><td>1 &frasl; 65,536</td></tr>
+ *     <tr><td>1 &frasl; 32</td><td>1 &frasl; 32,768</td></tr>
+ *     <tr><td>1 &frasl; 16</td><td>1 &frasl; 16,384</td></tr>
+ *     <tr><td>1 &frasl; 8</td><td>1 &frasl; 8,192</td></tr>
+ *     <tr><td>1 &frasl; 4</td><td>1 &frasl; 4,096</td></tr>
+ *     <tr><td>1 &frasl; 2</td><td>1 &frasl; 2,048</td></tr>
+ *     <tr><td>1</td><td>1 &frasl; 1,024</td></tr>
+ *     <tr><td>2</td><td>1 &frasl; 512</td></tr>
+ *     <tr><td>4</td><td>1 &frasl; 256</td></tr>
+ *     <tr><td>8</td><td>1 &frasl; 128</td></tr>
+ *     <tr><td>16</td><td>1 &frasl; 64</td></tr>
+ *     <tr><td>32</td><td>1 &frasl; 32</td></tr>
+ *     <tr><td>64</td><td>1 &frasl; 16</td></tr>
+ *     <tr><td>128</td><td>1 &frasl; 8</td></tr>
+ *     <tr><td>256</td><td>1 &frasl; 4</td></tr>
+ *     <tr><td>512</td><td>1 &frasl; 2</td></tr>
+ *     <tr><td>1,024</td><td>1</td></tr>
+ *     <tr><td>2,048</td><td>2</td></tr>
+ *     <tr><td>4,096</td><td>4</td></tr>
+ *     <tr><td>8,192</td><td>8</td></tr>
+ *     <tr><td>16,384</td><td>16</td></tr>
+ *     <tr><td>32,768</td><td>32</td></tr>
+ * </table>
+ *
+ * <p>This table shows that numbers higher than 1024 lose all fractional precision.</p>
+ */
+@SuppressWarnings("SimplifiableIfStatement")
+public final class Half extends Number implements Comparable<Half> {
+    /**
+     * The number of bits used to represent a half-precision float value.
+     */
+    public static final int SIZE = 16;
+
+    /**
+     * Epsilon is the difference between 1.0 and the next value representable
+     * by a half-precision floating-point.
+     */
+    public static final @HalfFloat short EPSILON = (short) 0x1400;
+
+    /**
+     * Maximum exponent a finite half-precision float may have.
+     */
+    public static final int MAX_EXPONENT = 15;
+    /**
+     * Minimum exponent a normalized half-precision float may have.
+     */
+    public static final int MIN_EXPONENT = -14;
+
+    /**
+     * Smallest negative value a half-precision float may have.
+     */
+    public static final @HalfFloat short LOWEST_VALUE = (short) 0xfbff;
+    /**
+     * Maximum positive finite value a half-precision float may have.
+     */
+    public static final @HalfFloat short MAX_VALUE = (short) 0x7bff;
+    /**
+     * Smallest positive normal value a half-precision float may have.
+     */
+    public static final @HalfFloat short MIN_NORMAL = (short) 0x0400;
+    /**
+     * Smallest positive non-zero value a half-precision float may have.
+     */
+    public static final @HalfFloat short MIN_VALUE = (short) 0x0001;
+    /**
+     * A Not-a-Number representation of a half-precision float.
+     */
+    public static final @HalfFloat short NaN = (short) 0x7e00;
+    /**
+     * Negative infinity of type half-precision float.
+     */
+    public static final @HalfFloat short NEGATIVE_INFINITY = (short) 0xfc00;
+    /**
+     * Negative 0 of type half-precision float.
+     */
+    public static final @HalfFloat short NEGATIVE_ZERO = (short) 0x8000;
+    /**
+     * Positive infinity of type half-precision float.
+     */
+    public static final @HalfFloat short POSITIVE_INFINITY = (short) 0x7c00;
+    /**
+     * Positive 0 of type half-precision float.
+     */
+    public static final @HalfFloat short POSITIVE_ZERO = (short) 0x0000;
+
+    private final @HalfFloat short mValue;
+
+    /**
+     * Constructs a newly allocated {@code Half} object that represents the
+     * half-precision float type argument.
+     *
+     * @param value The value to be represented by the {@code Half}
+     */
+    public Half(@HalfFloat short value) {
+        mValue = value;
+    }
+
+    /**
+     * Constructs a newly allocated {@code Half} object that represents the
+     * argument converted to a half-precision float.
+     *
+     * @param value The value to be represented by the {@code Half}
+     *
+     * @see #toHalf(float)
+     */
+    public Half(float value) {
+        mValue = toHalf(value);
+    }
+
+    /**
+     * Constructs a newly allocated {@code Half} object that
+     * represents the argument converted to a half-precision float.
+     *
+     * @param value The value to be represented by the {@code Half}
+     *
+     * @see #toHalf(float)
+     */
+    public Half(double value) {
+        mValue = toHalf((float) value);
+    }
+
+    /**
+     * <p>Constructs a newly allocated {@code Half} object that represents the
+     * half-precision float value represented by the string.
+     * The string is converted to a half-precision float value as if by the
+     * {@link #valueOf(String)} method.</p>
+     *
+     * <p>Calling this constructor is equivalent to calling:</p>
+     * <pre>
+     *     new Half(Float.parseFloat(value))
+     * </pre>
+     *
+     * @param value A string to be converted to a {@code Half}
+     * @throws NumberFormatException if the string does not contain a parsable number
+     *
+     * @see Float#valueOf(java.lang.String)
+     * @see #toHalf(float)
+     */
+    public Half(@NonNull String value) throws NumberFormatException {
+        mValue = toHalf(Float.parseFloat(value));
+    }
+
+    /**
+     * Returns the half-precision value of this {@code Half} as a {@code short}
+     * containing the bit representation described in {@link Half}.
+     *
+     * @return The half-precision float value represented by this object
+     */
+    public @HalfFloat short halfValue() {
+        return mValue;
+    }
+
+    /**
+     * Returns the value of this {@code Half} as a {@code byte} after
+     * a narrowing primitive conversion.
+     *
+     * @return The half-precision float value represented by this object
+     *         converted to type {@code byte}
+     */
+    @Override
+    public byte byteValue() {
+        return (byte) toFloat(mValue);
+    }
+
+    /**
+     * Returns the value of this {@code Half} as a {@code short} after
+     * a narrowing primitive conversion.
+     *
+     * @return The half-precision float value represented by this object
+     *         converted to type {@code short}
+     */
+    @Override
+    public short shortValue() {
+        return (short) toFloat(mValue);
+    }
+
+    /**
+     * Returns the value of this {@code Half} as a {@code int} after
+     * a narrowing primitive conversion.
+     *
+     * @return The half-precision float value represented by this object
+     *         converted to type {@code int}
+     */
+    @Override
+    public int intValue() {
+        return (int) toFloat(mValue);
+    }
+
+    /**
+     * Returns the value of this {@code Half} as a {@code long} after
+     * a narrowing primitive conversion.
+     *
+     * @return The half-precision float value represented by this object
+     *         converted to type {@code long}
+     */
+    @Override
+    public long longValue() {
+        return (long) toFloat(mValue);
+    }
+
+    /**
+     * Returns the value of this {@code Half} as a {@code float} after
+     * a widening primitive conversion.
+     *
+     * @return The half-precision float value represented by this object
+     *         converted to type {@code float}
+     */
+    @Override
+    public float floatValue() {
+        return toFloat(mValue);
+    }
+
+    /**
+     * Returns the value of this {@code Half} as a {@code double} after
+     * a widening primitive conversion.
+     *
+     * @return The half-precision float value represented by this object
+     *         converted to type {@code double}
+     */
+    @Override
+    public double doubleValue() {
+        return toFloat(mValue);
+    }
+
+    /**
+     * Returns true if this {@code Half} value represents a Not-a-Number,
+     * false otherwise.
+     *
+     * @return True if the value is a NaN, false otherwise
+     */
+    public boolean isNaN() {
+        return isNaN(mValue);
+    }
+
+    /**
+     * Compares this object against the specified object. The result is {@code true}
+     * if and only if the argument is not {@code null} and is a {@code Half} object
+     * that represents the same half-precision value as the this object. Two
+     * half-precision values are considered to be the same if and only if the method
+     * {@link #halfToIntBits(short)} returns an identical {@code int} value for both.
+     *
+     * @param o The object to compare
+     * @return True if the objects are the same, false otherwise
+     *
+     * @see #halfToIntBits(short)
+     */
+    @Override
+    public boolean equals(@Nullable Object o) {
+        return (o instanceof Half) &&
+                (halfToIntBits(((Half) o).mValue) == halfToIntBits(mValue));
+    }
+
+    /**
+     * Returns a hash code for this {@code Half} object. The result is the
+     * integer bit representation, exactly as produced by the method
+     * {@link #halfToIntBits(short)}, of the primitive half-precision float
+     * value represented by this {@code Half} object.
+     *
+     * @return A hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        return hashCode(mValue);
+    }
+
+    /**
+     * Returns a string representation of the specified half-precision
+     * float value. See {@link #toString(short)} for more information.
+     *
+     * @return A string representation of this {@code Half} object
+     */
+    @NonNull
+    @Override
+    public String toString() {
+        return toString(mValue);
+    }
+
+    /**
+     * <p>Compares the two specified half-precision float values. The following
+     * conditions apply during the comparison:</p>
+     *
+     * <ul>
+     * <li>{@link #NaN} is considered by this method to be equal to itself and greater
+     * than all other half-precision float values (including {@code #POSITIVE_INFINITY})</li>
+     * <li>{@link #POSITIVE_ZERO} is considered by this method to be greater than
+     * {@link #NEGATIVE_ZERO}.</li>
+     * </ul>
+     *
+     * @param h The half-precision float value to compare to the half-precision value
+     *          represented by this {@code Half} object
+     *
+     * @return  The value {@code 0} if {@code x} is numerically equal to {@code y}; a
+     *          value less than {@code 0} if {@code x} is numerically less than {@code y};
+     *          and a value greater than {@code 0} if {@code x} is numerically greater
+     *          than {@code y}
+     */
+    @Override
+    public int compareTo(@NonNull Half h) {
+        return compare(mValue, h.mValue);
+    }
+
+    /**
+     * Returns a hash code for a half-precision float value.
+     *
+     * @param h The value to hash
+     *
+     * @return A hash code value for a half-precision float value
+     */
+    public static int hashCode(@HalfFloat short h) {
+        return halfToIntBits(h);
+    }
+
+    /**
+     * <p>Compares the two specified half-precision float values. The following
+     * conditions apply during the comparison:</p>
+     *
+     * <ul>
+     * <li>{@link #NaN} is considered by this method to be equal to itself and greater
+     * than all other half-precision float values (including {@code #POSITIVE_INFINITY})</li>
+     * <li>{@link #POSITIVE_ZERO} is considered by this method to be greater than
+     * {@link #NEGATIVE_ZERO}.</li>
+     * </ul>
+     *
+     * @param x The first half-precision float value to compare.
+     * @param y The second half-precision float value to compare
+     *
+     * @return  The value {@code 0} if {@code x} is numerically equal to {@code y}, a
+     *          value less than {@code 0} if {@code x} is numerically less than {@code y},
+     *          and a value greater than {@code 0} if {@code x} is numerically greater
+     *          than {@code y}
+     */
+    public static int compare(@HalfFloat short x, @HalfFloat short y) {
+        return FP16.compare(x, y);
+    }
+
+    /**
+     * <p>Returns a representation of the specified half-precision float value
+     * according to the bit layout described in {@link Half}.</p>
+     *
+     * <p>Similar to {@link #halfToIntBits(short)}, this method collapses all
+     * possible Not-a-Number values to a single canonical Not-a-Number value
+     * defined by {@link #NaN}.</p>
+     *
+     * @param h A half-precision float value
+     * @return The bits that represent the half-precision float value
+     *
+     * @see #halfToIntBits(short)
+     */
+    public static @HalfFloat short halfToShortBits(@HalfFloat short h) {
+        return (h & FP16.EXPONENT_SIGNIFICAND_MASK) > FP16.POSITIVE_INFINITY ? NaN : h;
+    }
+
+    /**
+     * <p>Returns a representation of the specified half-precision float value
+     * according to the bit layout described in {@link Half}.</p>
+     *
+     * <p>Unlike {@link #halfToRawIntBits(short)}, this method collapses all
+     * possible Not-a-Number values to a single canonical Not-a-Number value
+     * defined by {@link #NaN}.</p>
+     *
+     * @param h A half-precision float value
+     * @return The bits that represent the half-precision float value
+     *
+     * @see #halfToRawIntBits(short)
+     * @see #halfToShortBits(short)
+     * @see #intBitsToHalf(int)
+     */
+    public static int halfToIntBits(@HalfFloat short h) {
+        return (h & FP16.EXPONENT_SIGNIFICAND_MASK) > FP16.POSITIVE_INFINITY ? NaN : h & 0xffff;
+    }
+
+    /**
+     * <p>Returns a representation of the specified half-precision float value
+     * according to the bit layout described in {@link Half}.</p>
+     *
+     * <p>The argument is considered to be a representation of a half-precision
+     * float value according to the bit layout described in {@link Half}. The 16
+     * most significant bits of the returned value are set to 0.</p>
+     *
+     * @param h A half-precision float value
+     * @return The bits that represent the half-precision float value
+     *
+     * @see #halfToIntBits(short)
+     * @see #intBitsToHalf(int)
+     */
+    public static int halfToRawIntBits(@HalfFloat short h) {
+        return h & 0xffff;
+    }
+
+    /**
+     * <p>Returns the half-precision float value corresponding to a given
+     * bit representation.</p>
+     *
+     * <p>The argument is considered to be a representation of a half-precision
+     * float value according to the bit layout described in {@link Half}. The 16
+     * most significant bits of the argument are ignored.</p>
+     *
+     * @param bits An integer
+     * @return The half-precision float value with the same bit pattern
+     */
+    public static @HalfFloat short intBitsToHalf(int bits) {
+        return (short) (bits & 0xffff);
+    }
+
+    /**
+     * Returns the first parameter with the sign of the second parameter.
+     * This method treats NaNs as having a sign.
+     *
+     * @param magnitude A half-precision float value providing the magnitude of the result
+     * @param sign  A half-precision float value providing the sign of the result
+     * @return A value with the magnitude of the first parameter and the sign
+     *         of the second parameter
+     */
+    public static @HalfFloat short copySign(@HalfFloat short magnitude, @HalfFloat short sign) {
+        return (short) ((sign & FP16.SIGN_MASK) | (magnitude & FP16.EXPONENT_SIGNIFICAND_MASK));
+    }
+
+    /**
+     * Returns the absolute value of the specified half-precision float.
+     * Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is positive zero (see {@link #POSITIVE_ZERO})</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is positive infinity (see {@link #POSITIVE_INFINITY})</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The absolute value of the specified half-precision float
+     */
+    public static @HalfFloat short abs(@HalfFloat short h) {
+        return (short) (h & FP16.EXPONENT_SIGNIFICAND_MASK);
+    }
+
+    /**
+     * Returns the closest integral half-precision float value to the specified
+     * half-precision float value. Special values are handled in the
+     * following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * <p class=note>
+     * <strong>Note:</strong> Unlike the identically named
+     * <code class=prettyprint>int java.lang.Math.round(float)</code> method,
+     * this returns a Half value stored in a short, <strong>not</strong> an
+     * actual short integer result.
+     *
+     * @param h A half-precision float value
+     * @return The value of the specified half-precision float rounded to the nearest
+     *         half-precision float value
+     */
+    public static @HalfFloat short round(@HalfFloat short h) {
+        return FP16.rint(h);
+    }
+
+    /**
+     * Returns the smallest half-precision float value toward negative infinity
+     * greater than or equal to the specified half-precision float value.
+     * Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The smallest half-precision float value toward negative infinity
+     *         greater than or equal to the specified half-precision float value
+     */
+    public static @HalfFloat short ceil(@HalfFloat short h) {
+        return FP16.ceil(h);
+    }
+
+    /**
+     * Returns the largest half-precision float value toward positive infinity
+     * less than or equal to the specified half-precision float value.
+     * Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The largest half-precision float value toward positive infinity
+     *         less than or equal to the specified half-precision float value
+     */
+    public static @HalfFloat short floor(@HalfFloat short h) {
+        return FP16.floor(h);
+    }
+
+    /**
+     * Returns the truncated half-precision float value of the specified
+     * half-precision float value. Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The truncated half-precision float value of the specified
+     *         half-precision float value
+     */
+    public static @HalfFloat short trunc(@HalfFloat short h) {
+        return FP16.trunc(h);
+    }
+
+    /**
+     * Returns the smaller of two half-precision float values (the value closest
+     * to negative infinity). Special values are handled in the following ways:
+     * <ul>
+     * <li>If either value is NaN, the result is NaN</li>
+     * <li>{@link #NEGATIVE_ZERO} is smaller than {@link #POSITIVE_ZERO}</li>
+     * </ul>
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     * @return The smaller of the two specified half-precision values
+     */
+    public static @HalfFloat short min(@HalfFloat short x, @HalfFloat short y) {
+        return FP16.min(x, y);
+    }
+
+    /**
+     * Returns the larger of two half-precision float values (the value closest
+     * to positive infinity). Special values are handled in the following ways:
+     * <ul>
+     * <li>If either value is NaN, the result is NaN</li>
+     * <li>{@link #POSITIVE_ZERO} is greater than {@link #NEGATIVE_ZERO}</li>
+     * </ul>
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return The larger of the two specified half-precision values
+     */
+    public static @HalfFloat short max(@HalfFloat short x, @HalfFloat short y) {
+        return FP16.max(x, y);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is less (smaller
+     * toward negative infinity) than the second half-precision float value.
+     * If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is less than y, false otherwise
+     */
+    public static boolean less(@HalfFloat short x, @HalfFloat short y) {
+        return FP16.less(x, y);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is less (smaller
+     * toward negative infinity) than or equal to the second half-precision
+     * float value. If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is less than or equal to y, false otherwise
+     */
+    public static boolean lessEquals(@HalfFloat short x, @HalfFloat short y) {
+        return FP16.lessEquals(x, y);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is greater (larger
+     * toward positive infinity) than the second half-precision float value.
+     * If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is greater than y, false otherwise
+     */
+    public static boolean greater(@HalfFloat short x, @HalfFloat short y) {
+        return FP16.greater(x, y);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is greater (larger
+     * toward positive infinity) than or equal to the second half-precision float
+     * value. If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is greater than y, false otherwise
+     */
+    public static boolean greaterEquals(@HalfFloat short x, @HalfFloat short y) {
+        return FP16.greaterEquals(x, y);
+    }
+
+    /**
+     * Returns true if the two half-precision float values are equal.
+     * If either of the values is NaN, the result is false. {@link #POSITIVE_ZERO}
+     * and {@link #NEGATIVE_ZERO} are considered equal.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is equal to y, false otherwise
+     */
+    public static boolean equals(@HalfFloat short x, @HalfFloat short y) {
+        return FP16.equals(x, y);
+    }
+
+    /**
+     * Returns the sign of the specified half-precision float.
+     *
+     * @param h A half-precision float value
+     * @return 1 if the value is positive, -1 if the value is negative
+     */
+    public static int getSign(@HalfFloat short h) {
+        return (h & FP16.SIGN_MASK) == 0 ? 1 : -1;
+    }
+
+    /**
+     * Returns the unbiased exponent used in the representation of
+     * the specified  half-precision float value. if the value is NaN
+     * or infinite, this* method returns {@link #MAX_EXPONENT} + 1.
+     * If the argument is 0 or a subnormal representation, this method
+     * returns {@link #MIN_EXPONENT} - 1.
+     *
+     * @param h A half-precision float value
+     * @return The unbiased exponent of the specified value
+     */
+    public static int getExponent(@HalfFloat short h) {
+        return ((h >>> FP16.EXPONENT_SHIFT) & FP16.SHIFTED_EXPONENT_MASK) - FP16.EXPONENT_BIAS;
+    }
+
+    /**
+     * Returns the significand, or mantissa, used in the representation
+     * of the specified half-precision float value.
+     *
+     * @param h A half-precision float value
+     * @return The significand, or significand, of the specified vlaue
+     */
+    public static int getSignificand(@HalfFloat short h) {
+        return h & FP16.SIGNIFICAND_MASK;
+    }
+
+    /**
+     * Returns true if the specified half-precision float value represents
+     * infinity, false otherwise.
+     *
+     * @param h A half-precision float value
+     * @return True if the value is positive infinity or negative infinity,
+     *         false otherwise
+     */
+    public static boolean isInfinite(@HalfFloat short h) {
+        return FP16.isInfinite(h);
+    }
+
+    /**
+     * Returns true if the specified half-precision float value represents
+     * a Not-a-Number, false otherwise.
+     *
+     * @param h A half-precision float value
+     * @return True if the value is a NaN, false otherwise
+     */
+    public static boolean isNaN(@HalfFloat short h) {
+        return FP16.isNaN(h);
+    }
+
+    /**
+     * Returns true if the specified half-precision float value is normalized
+     * (does not have a subnormal representation). If the specified value is
+     * {@link #POSITIVE_INFINITY}, {@link #NEGATIVE_INFINITY},
+     * {@link #POSITIVE_ZERO}, {@link #NEGATIVE_ZERO}, NaN or any subnormal
+     * number, this method returns false.
+     *
+     * @param h A half-precision float value
+     * @return True if the value is normalized, false otherwise
+     */
+    public static boolean isNormalized(@HalfFloat short h) {
+        return FP16.isNormalized(h);
+    }
+
+    /**
+     * <p>Converts the specified half-precision float value into a
+     * single-precision float value. The following special cases are handled:</p>
+     * <ul>
+     * <li>If the input is {@link #NaN}, the returned value is {@link Float#NaN}</li>
+     * <li>If the input is {@link #POSITIVE_INFINITY} or
+     * {@link #NEGATIVE_INFINITY}, the returned value is respectively
+     * {@link Float#POSITIVE_INFINITY} or {@link Float#NEGATIVE_INFINITY}</li>
+     * <li>If the input is 0 (positive or negative), the returned value is +/-0.0f</li>
+     * <li>Otherwise, the returned value is a normalized single-precision float value</li>
+     * </ul>
+     *
+     * @param h The half-precision float value to convert to single-precision
+     * @return A normalized single-precision float value
+     */
+    public static float toFloat(@HalfFloat short h) {
+        return FP16.toFloat(h);
+    }
+
+    /**
+     * <p>Converts the specified single-precision float value into a
+     * half-precision float value. The following special cases are handled:</p>
+     * <ul>
+     * <li>If the input is NaN (see {@link Float#isNaN(float)}), the returned
+     * value is {@link #NaN}</li>
+     * <li>If the input is {@link Float#POSITIVE_INFINITY} or
+     * {@link Float#NEGATIVE_INFINITY}, the returned value is respectively
+     * {@link #POSITIVE_INFINITY} or {@link #NEGATIVE_INFINITY}</li>
+     * <li>If the input is 0 (positive or negative), the returned value is
+     * {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+     * <li>If the input is a less than {@link #MIN_VALUE}, the returned value
+     * is flushed to {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+     * <li>If the input is a less than {@link #MIN_NORMAL}, the returned value
+     * is a denorm half-precision float</li>
+     * <li>Otherwise, the returned value is rounded to the nearest
+     * representable half-precision float value</li>
+     * </ul>
+     *
+     * @param f The single-precision float value to convert to half-precision
+     * @return A half-precision float value
+     */
+    @SuppressWarnings("StatementWithEmptyBody")
+    public static @HalfFloat short toHalf(float f) {
+        return FP16.toHalf(f);
+    }
+
+    /**
+     * Returns a {@code Half} instance representing the specified
+     * half-precision float value.
+     *
+     * @param h A half-precision float value
+     * @return a {@code Half} instance representing {@code h}
+     */
+    public static @NonNull Half valueOf(@HalfFloat short h) {
+        return new Half(h);
+    }
+
+    /**
+     * Returns a {@code Half} instance representing the specified float value.
+     *
+     * @param f A float value
+     * @return a {@code Half} instance representing {@code f}
+     */
+    public static @NonNull Half valueOf(float f) {
+        return new Half(f);
+    }
+
+    /**
+     * Returns a {@code Half} instance representing the specified string value.
+     * Calling this method is equivalent to calling
+     * <code>toHalf(Float.parseString(h))</code>. See {@link Float#valueOf(String)}
+     * for more information on the format of the string representation.
+     *
+     * @param s The string to be parsed
+     * @return a {@code Half} instance representing {@code h}
+     * @throws NumberFormatException if the string does not contain a parsable
+     *         half-precision float value
+     */
+    public static @NonNull Half valueOf(@NonNull String s) {
+        return new Half(s);
+    }
+
+    /**
+     * Returns the half-precision float value represented by the specified string.
+     * Calling this method is equivalent to calling
+     * <code>toHalf(Float.parseString(h))</code>. See {@link Float#valueOf(String)}
+     * for more information on the format of the string representation.
+     *
+     * @param s The string to be parsed
+     * @return A half-precision float value represented by the string
+     * @throws NumberFormatException if the string does not contain a parsable
+     *         half-precision float value
+     */
+    public static @HalfFloat short parseHalf(@NonNull String s) throws NumberFormatException {
+        return toHalf(Float.parseFloat(s));
+    }
+
+    /**
+     * Returns a string representation of the specified half-precision
+     * float value. Calling this method is equivalent to calling
+     * <code>Float.toString(toFloat(h))</code>. See {@link Float#toString(float)}
+     * for more information on the format of the string representation.
+     *
+     * @param h A half-precision float value
+     * @return A string representation of the specified value
+     */
+    @NonNull
+    public static String toString(@HalfFloat short h) {
+        return Float.toString(toFloat(h));
+    }
+
+    /**
+     * <p>Returns a hexadecimal string representation of the specified half-precision
+     * float value. If the value is a NaN, the result is <code>"NaN"</code>,
+     * otherwise the result follows this format:</p>
+     * <ul>
+     * <li>If the sign is positive, no sign character appears in the result</li>
+     * <li>If the sign is negative, the first character is <code>'-'</code></li>
+     * <li>If the value is inifinity, the string is <code>"Infinity"</code></li>
+     * <li>If the value is 0, the string is <code>"0x0.0p0"</code></li>
+     * <li>If the value has a normalized representation, the exponent and
+     * significand are represented in the string in two fields. The significand
+     * starts with <code>"0x1."</code> followed by its lowercase hexadecimal
+     * representation. Trailing zeroes are removed unless all digits are 0, then
+     * a single zero is used. The significand representation is followed by the
+     * exponent, represented by <code>"p"</code>, itself followed by a decimal
+     * string of the unbiased exponent</li>
+     * <li>If the value has a subnormal representation, the significand starts
+     * with <code>"0x0."</code> followed by its lowercase hexadecimal
+     * representation. Trailing zeroes are removed unless all digits are 0, then
+     * a single zero is used. The significand representation is followed by the
+     * exponent, represented by <code>"p-14"</code></li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return A hexadecimal string representation of the specified value
+     */
+    @NonNull
+    public static String toHexString(@HalfFloat short h) {
+        return FP16.toHexString(h);
+    }
+}
diff --git a/android/util/HashedStringCache.java b/android/util/HashedStringCache.java
new file mode 100644
index 0000000..1f2b956
--- /dev/null
+++ b/android/util/HashedStringCache.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+/**
+ * HashedStringCache provides hashing functionality with an underlying LRUCache and expiring salt.
+ * Salt and expiration time are being stored under the tag passed in by the calling package --
+ * intended usage is the calling package name.
+ * @hide
+ */
+public class HashedStringCache {
+    private static HashedStringCache sHashedStringCache = null;
+    private static final Charset UTF_8 = Charset.forName("UTF-8");
+    private static final int HASH_CACHE_SIZE = 100;
+    private static final int HASH_LENGTH = 8;
+    @VisibleForTesting
+    static final String HASH_SALT = "_hash_salt";
+    @VisibleForTesting
+    static final String HASH_SALT_DATE = "_hash_salt_date";
+    @VisibleForTesting
+    static final String HASH_SALT_GEN = "_hash_salt_gen";
+    // For privacy we need to rotate the salt regularly
+    private static final long DAYS_TO_MILLIS = 1000 * 60 * 60 * 24;
+    private static final int MAX_SALT_DAYS = 100;
+    private final LruCache<String, String> mHashes;
+    private final SecureRandom mSecureRandom;
+    private final Object mPreferenceLock = new Object();
+    private final MessageDigest mDigester;
+    private byte[] mSalt;
+    private int mSaltGen;
+    private SharedPreferences mSharedPreferences;
+
+    private static final String TAG = "HashedStringCache";
+    private static final boolean DEBUG = false;
+
+    private HashedStringCache() {
+        mHashes = new LruCache<>(HASH_CACHE_SIZE);
+        mSecureRandom = new SecureRandom();
+        try {
+            mDigester = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException impossible) {
+            // this can't happen - MD5 is always present
+            throw new RuntimeException(impossible);
+        }
+    }
+
+    /**
+     * @return - instance of the HashedStringCache
+     * @hide
+     */
+    public static HashedStringCache getInstance() {
+        if (sHashedStringCache == null) {
+            sHashedStringCache = new HashedStringCache();
+        }
+        return sHashedStringCache;
+    }
+
+    /**
+     * Take the string and context and create a hash of the string. Trigger refresh on salt if salt
+     * is more than 7 days old
+     * @param context - callers context to retrieve SharedPreferences
+     * @param clearText - string that needs to be hashed
+     * @param tag - class name to use for storing values in shared preferences
+     * @param saltExpirationDays - number of days we may keep the same salt
+     *                           special value -1 will short-circuit and always return null.
+     * @return - HashResult containing the hashed string and the generation of the hash salt, null
+     *      if clearText string is empty
+     *
+     * @hide
+     */
+    public HashResult hashString(Context context, String tag, String clearText,
+            int saltExpirationDays) {
+        if (saltExpirationDays == -1 || context == null
+                || TextUtils.isEmpty(clearText) || TextUtils.isEmpty(tag)) {
+            return null;
+        }
+
+        populateSaltValues(context, tag, saltExpirationDays);
+        String hashText = mHashes.get(clearText);
+        if (hashText != null) {
+            return new HashResult(hashText, mSaltGen);
+        }
+
+        mDigester.reset();
+        mDigester.update(mSalt);
+        mDigester.update(clearText.getBytes(UTF_8));
+        byte[] bytes = mDigester.digest();
+        int len = Math.min(HASH_LENGTH, bytes.length);
+        hashText = Base64.encodeToString(bytes, 0, len, Base64.NO_PADDING | Base64.NO_WRAP);
+        mHashes.put(clearText, hashText);
+
+        return new HashResult(hashText, mSaltGen);
+    }
+
+    /**
+     * Populates the mSharedPreferences and checks if there is a salt present and if it's older than
+     * 7 days
+     * @param tag - class name to use for storing values in shared preferences
+     * @param saltExpirationDays - number of days we may keep the same salt
+     * @param saltDate - the date retrieved from configuration
+     * @return - true if no salt or salt is older than 7 days
+     */
+    private boolean checkNeedsNewSalt(String tag, int saltExpirationDays, long saltDate) {
+        if (saltDate == 0 || saltExpirationDays < -1) {
+            return true;
+        }
+        if (saltExpirationDays > MAX_SALT_DAYS) {
+            saltExpirationDays = MAX_SALT_DAYS;
+        }
+        long now = System.currentTimeMillis();
+        long delta = now - saltDate;
+        // Check for delta < 0 to make sure we catch if someone puts their phone far in the
+        // future and then goes back to normal time.
+        return delta >= saltExpirationDays * DAYS_TO_MILLIS || delta < 0;
+    }
+
+    /**
+     * Populate the salt and saltGen member variables if they aren't already set / need refreshing.
+     * @param context - to get sharedPreferences
+     * @param tag - class name to use for storing values in shared preferences
+     * @param saltExpirationDays - number of days we may keep the same salt
+     */
+    private void populateSaltValues(Context context, String tag, int saltExpirationDays) {
+        synchronized (mPreferenceLock) {
+            // check if we need to refresh the salt
+            mSharedPreferences = getHashSharedPreferences(context);
+            long saltDate = mSharedPreferences.getLong(tag + HASH_SALT_DATE, 0);
+            boolean needsNewSalt = checkNeedsNewSalt(tag, saltExpirationDays, saltDate);
+            if (needsNewSalt) {
+                mHashes.evictAll();
+            }
+            if (mSalt == null || needsNewSalt) {
+                String saltString = mSharedPreferences.getString(tag + HASH_SALT, null);
+                mSaltGen = mSharedPreferences.getInt(tag + HASH_SALT_GEN, 0);
+                if (saltString == null || needsNewSalt) {
+                    mSaltGen++;
+                    byte[] saltBytes = new byte[16];
+                    mSecureRandom.nextBytes(saltBytes);
+                    saltString = Base64.encodeToString(saltBytes,
+                            Base64.NO_PADDING | Base64.NO_WRAP);
+                    mSharedPreferences.edit()
+                            .putString(tag + HASH_SALT, saltString)
+                            .putInt(tag + HASH_SALT_GEN, mSaltGen)
+                            .putLong(tag + HASH_SALT_DATE, System.currentTimeMillis()).apply();
+                    if (DEBUG) {
+                        Log.d(TAG, "created a new salt: " + saltString);
+                    }
+                }
+                mSalt = saltString.getBytes(UTF_8);
+            }
+        }
+    }
+
+    /**
+     * Android:ui doesn't have persistent preferences, so need to fall back on this hack originally
+     * from ChooserActivity.java
+     * @param context
+     * @return
+     */
+    private SharedPreferences getHashSharedPreferences(Context context) {
+        final File prefsFile = new File(new File(
+                Environment.getDataUserCePackageDirectory(
+                        StorageManager.UUID_PRIVATE_INTERNAL,
+                        context.getUserId(), context.getPackageName()),
+                "shared_prefs"),
+                "hashed_cache.xml");
+        return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Helper class to hold hashed string and salt generation.
+     */
+    public class HashResult {
+        public String hashedString;
+        public int saltGeneration;
+
+        public HashResult(String hString, int saltGen) {
+            hashedString = hString;
+            saltGeneration = saltGen;
+        }
+    }
+}
diff --git a/android/util/IconDrawableFactory.java b/android/util/IconDrawableFactory.java
new file mode 100644
index 0000000..721e6b3
--- /dev/null
+++ b/android/util/IconDrawableFactory.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+/**
+ * Utility class to load app drawables with appropriate badging.
+ *
+ * @hide
+ */
+public class IconDrawableFactory {
+
+    protected final Context mContext;
+    protected final PackageManager mPm;
+    protected final UserManager mUm;
+    protected final LauncherIcons mLauncherIcons;
+    protected final boolean mEmbedShadow;
+
+    private IconDrawableFactory(Context context, boolean embedShadow) {
+        mContext = context;
+        mPm = context.getPackageManager();
+        mUm = context.getSystemService(UserManager.class);
+        mLauncherIcons = new LauncherIcons(context);
+        mEmbedShadow = embedShadow;
+    }
+
+    protected boolean needsBadging(ApplicationInfo appInfo, @UserIdInt int userId) {
+        return appInfo.isInstantApp() || mUm.isManagedProfile(userId);
+    }
+
+    @UnsupportedAppUsage
+    public Drawable getBadgedIcon(ApplicationInfo appInfo) {
+        return getBadgedIcon(appInfo, UserHandle.getUserId(appInfo.uid));
+    }
+
+    public Drawable getBadgedIcon(ApplicationInfo appInfo, @UserIdInt int userId) {
+        return getBadgedIcon(appInfo, appInfo, userId);
+    }
+
+    @UnsupportedAppUsage
+    public Drawable getBadgedIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo,
+            @UserIdInt int userId) {
+        Drawable icon = mPm.loadUnbadgedItemIcon(itemInfo, appInfo);
+        if (!mEmbedShadow && !needsBadging(appInfo, userId)) {
+            return icon;
+        }
+
+        icon = getShadowedIcon(icon);
+        if (appInfo.isInstantApp()) {
+            int badgeColor = Resources.getSystem().getColor(
+                    com.android.internal.R.color.instant_app_badge, null);
+            icon = mLauncherIcons.getBadgedDrawable(icon,
+                    com.android.internal.R.drawable.ic_instant_icon_badge_bolt,
+                    badgeColor);
+        }
+        if (mUm.hasBadge(userId)) {
+            icon = mLauncherIcons.getBadgedDrawable(icon,
+                    mUm.getUserIconBadgeResId(userId),
+                    mUm.getUserBadgeColor(userId));
+        }
+        return icon;
+    }
+
+    /**
+     * Add shadow to the icon if {@link AdaptiveIconDrawable}
+     */
+    public Drawable getShadowedIcon(Drawable icon) {
+        return mLauncherIcons.wrapIconDrawableWithShadow(icon);
+    }
+
+    @UnsupportedAppUsage
+    public static IconDrawableFactory newInstance(Context context) {
+        return new IconDrawableFactory(context, true);
+    }
+
+    public static IconDrawableFactory newInstance(Context context, boolean embedShadow) {
+        return new IconDrawableFactory(context, embedShadow);
+    }
+}
diff --git a/android/util/IntArray.java b/android/util/IntArray.java
new file mode 100644
index 0000000..5a74ec0
--- /dev/null
+++ b/android/util/IntArray.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+
+/**
+ * Implements a growing array of int primitives.
+ *
+ * @hide
+ */
+public class IntArray implements Cloneable {
+    private static final int MIN_CAPACITY_INCREMENT = 12;
+
+    private int[] mValues;
+    private int mSize;
+
+    private  IntArray(int[] array, int size) {
+        mValues = array;
+        mSize = Preconditions.checkArgumentInRange(size, 0, array.length, "size");
+    }
+
+    /**
+     * Creates an empty IntArray with the default initial capacity.
+     */
+    public IntArray() {
+        this(10);
+    }
+
+    /**
+     * Creates an empty IntArray with the specified initial capacity.
+     */
+    public IntArray(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mValues = EmptyArray.INT;
+        } else {
+            mValues = ArrayUtils.newUnpaddedIntArray(initialCapacity);
+        }
+        mSize = 0;
+    }
+
+    /**
+     * Creates an IntArray wrapping the given primitive int array.
+     */
+    public static IntArray wrap(int[] array) {
+        return new IntArray(array, array.length);
+    }
+
+    /**
+     * Creates an IntArray from the given primitive int array, copying it.
+     */
+    public static IntArray fromArray(int[] array, int size) {
+        return wrap(Arrays.copyOf(array, size));
+    }
+
+    /**
+     * Changes the size of this IntArray. If this IntArray is shrinked, the backing array capacity
+     * is unchanged. If the new size is larger than backing array capacity, a new backing array is
+     * created from the current content of this IntArray padded with 0s.
+     */
+    public void resize(int newSize) {
+        Preconditions.checkArgumentNonnegative(newSize);
+        if (newSize <= mValues.length) {
+            Arrays.fill(mValues, newSize, mValues.length, 0);
+        } else {
+            ensureCapacity(newSize - mSize);
+        }
+        mSize = newSize;
+    }
+
+    /**
+     * Appends the specified value to the end of this array.
+     */
+    public void add(int value) {
+        add(mSize, value);
+    }
+
+    /**
+     * Inserts a value at the specified position in this array. If the specified index is equal to
+     * the length of the array, the value is added at the end.
+     *
+     * @throws IndexOutOfBoundsException when index &lt; 0 || index &gt; size()
+     */
+    public void add(int index, int value) {
+        ensureCapacity(1);
+        int rightSegment = mSize - index;
+        mSize++;
+        ArrayUtils.checkBounds(mSize, index);
+
+        if (rightSegment != 0) {
+            // Move by 1 all values from the right of 'index'
+            System.arraycopy(mValues, index, mValues, index + 1, rightSegment);
+        }
+
+        mValues[index] = value;
+    }
+
+    /**
+     * Searches the array for the specified value using the binary search algorithm. The array must
+     * be sorted (as by the {@link Arrays#sort(int[], int, int)} method) prior to making this call.
+     * If it is not sorted, the results are undefined. If the range contains multiple elements with
+     * the specified value, there is no guarantee which one will be found.
+     *
+     * @param value The value to search for.
+     * @return index of the search key, if it is contained in the array; otherwise, <i>(-(insertion
+     *         point) - 1)</i>. The insertion point is defined as the point at which the key would
+     *         be inserted into the array: the index of the first element greater than the key, or
+     *         {@link #size()} if all elements in the array are less than the specified key.
+     *         Note that this guarantees that the return value will be >= 0 if and only if the key
+     *         is found.
+     */
+    public int binarySearch(int value) {
+        return ContainerHelpers.binarySearch(mValues, mSize, value);
+    }
+
+    /**
+     * Adds the values in the specified array to this array.
+     */
+    public void addAll(IntArray values) {
+        final int count = values.mSize;
+        ensureCapacity(count);
+
+        System.arraycopy(values.mValues, 0, mValues, mSize, count);
+        mSize += count;
+    }
+
+    /**
+     * Ensures capacity to append at least <code>count</code> values.
+     */
+    private void ensureCapacity(int count) {
+        final int currentSize = mSize;
+        final int minCapacity = currentSize + count;
+        if (minCapacity >= mValues.length) {
+            final int targetCap = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2) ?
+                    MIN_CAPACITY_INCREMENT : currentSize >> 1);
+            final int newCapacity = targetCap > minCapacity ? targetCap : minCapacity;
+            final int[] newValues = ArrayUtils.newUnpaddedIntArray(newCapacity);
+            System.arraycopy(mValues, 0, newValues, 0, currentSize);
+            mValues = newValues;
+        }
+    }
+
+    /**
+     * Removes all values from this array.
+     */
+    public void clear() {
+        mSize = 0;
+    }
+
+    @Override
+    public IntArray clone() throws CloneNotSupportedException {
+        final IntArray clone = (IntArray) super.clone();
+        clone.mValues = mValues.clone();
+        return clone;
+    }
+
+    /**
+     * Returns the value at the specified position in this array.
+     */
+    public int get(int index) {
+        ArrayUtils.checkBounds(mSize, index);
+        return mValues[index];
+    }
+
+    /**
+     * Sets the value at the specified position in this array.
+     */
+    public void set(int index, int value) {
+        ArrayUtils.checkBounds(mSize, index);
+        mValues[index] = value;
+    }
+
+    /**
+     * Returns the index of the first occurrence of the specified value in this
+     * array, or -1 if this array does not contain the value.
+     */
+    public int indexOf(int value) {
+        final int n = mSize;
+        for (int i = 0; i < n; i++) {
+            if (mValues[i] == value) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Removes the value at the specified index from this array.
+     */
+    public void remove(int index) {
+        ArrayUtils.checkBounds(mSize, index);
+        System.arraycopy(mValues, index + 1, mValues, index, mSize - index - 1);
+        mSize--;
+    }
+
+    /**
+     * Returns the number of values in this array.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Returns a new array with the contents of this IntArray.
+     */
+    public int[] toArray() {
+        return Arrays.copyOf(mValues, mSize);
+    }
+}
diff --git a/android/util/IntProperty.java b/android/util/IntProperty.java
new file mode 100644
index 0000000..9e21ced
--- /dev/null
+++ b/android/util/IntProperty.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.util;
+
+/**
+ * An implementation of {@link android.util.Property} to be used specifically with fields of type
+ * <code>int</code>. This type-specific subclass enables performance benefit by allowing
+ * calls to a {@link #setValue(Object, int) setValue()} function that takes the primitive
+ * <code>int</code> type and avoids autoboxing and other overhead associated with the
+ * <code>Integer</code> class.
+ *
+ * @param <T> The class on which the Property is declared.
+ */
+public abstract class IntProperty<T> extends Property<T, Integer> {
+
+    public IntProperty(String name) {
+        super(Integer.class, name);
+    }
+
+    /**
+     * A type-specific variant of {@link #set(Object, Integer)} that is faster when dealing
+     * with fields of type <code>int</code>.
+     */
+    public abstract void setValue(T object, int value);
+
+    @Override
+    final public void set(T object, Integer value) {
+        setValue(object, value.intValue());
+    }
+
+}
\ No newline at end of file
diff --git a/android/util/JsonReader.java b/android/util/JsonReader.java
new file mode 100644
index 0000000..50f63f8
--- /dev/null
+++ b/android/util/JsonReader.java
@@ -0,0 +1,1173 @@
+/*
+ * Copyright (C) 2010 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;
+
+import libcore.internal.StringPool;
+
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value as a stream of tokens. This stream includes both literal
+ * values (strings, numbers, booleans, and nulls) as well as the begin and
+ * end delimiters of objects and arrays. The tokens are traversed in
+ * depth-first order, the same order that they appear in the JSON document.
+ * Within JSON objects, name/value pairs are represented by a single token.
+ *
+ * <h3>Parsing JSON</h3>
+ * To create a recursive descent parser for your own JSON streams, first create
+ * an entry point method that creates a {@code JsonReader}.
+ *
+ * <p>Next, create handler methods for each structure in your JSON text. You'll
+ * need a method for each object type and for each array type.
+ * <ul>
+ *   <li>Within <strong>array handling</strong> methods, first call {@link
+ *       #beginArray} to consume the array's opening bracket. Then create a
+ *       while loop that accumulates values, terminating when {@link #hasNext}
+ *       is false. Finally, read the array's closing bracket by calling {@link
+ *       #endArray}.
+ *   <li>Within <strong>object handling</strong> methods, first call {@link
+ *       #beginObject} to consume the object's opening brace. Then create a
+ *       while loop that assigns values to local variables based on their name.
+ *       This loop should terminate when {@link #hasNext} is false. Finally,
+ *       read the object's closing brace by calling {@link #endObject}.
+ * </ul>
+ * <p>When a nested object or array is encountered, delegate to the
+ * corresponding handler method.
+ *
+ * <p>When an unknown name is encountered, strict parsers should fail with an
+ * exception. Lenient parsers should call {@link #skipValue()} to recursively
+ * skip the value's nested tokens, which may otherwise conflict.
+ *
+ * <p>If a value may be null, you should first check using {@link #peek()}.
+ * Null literals can be consumed using either {@link #nextNull()} or {@link
+ * #skipValue()}.
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code
+ * [
+ *   {
+ *     "id": 912345678901,
+ *     "text": "How do I read JSON on Android?",
+ *     "geo": null,
+ *     "user": {
+ *       "name": "android_newb",
+ *       "followers_count": 41
+ *      }
+ *   },
+ *   {
+ *     "id": 912345678902,
+ *     "text": "@android_newb just use android.util.JsonReader!",
+ *     "geo": [50.454722, -104.606667],
+ *     "user": {
+ *       "name": "jesse",
+ *       "followers_count": 2
+ *     }
+ *   }
+ * ]}</pre>
+ * This code implements the parser for the above structure: <pre>   {@code
+ *
+ *   public List<Message> readJsonStream(InputStream in) throws IOException {
+ *     JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+ *     try {
+ *       return readMessagesArray(reader);
+ *     } finally {
+ *       reader.close();
+ *     }
+ *   }
+ *
+ *   public List<Message> readMessagesArray(JsonReader reader) throws IOException {
+ *     List<Message> messages = new ArrayList<Message>();
+ *
+ *     reader.beginArray();
+ *     while (reader.hasNext()) {
+ *       messages.add(readMessage(reader));
+ *     }
+ *     reader.endArray();
+ *     return messages;
+ *   }
+ *
+ *   public Message readMessage(JsonReader reader) throws IOException {
+ *     long id = -1;
+ *     String text = null;
+ *     User user = null;
+ *     List<Double> geo = null;
+ *
+ *     reader.beginObject();
+ *     while (reader.hasNext()) {
+ *       String name = reader.nextName();
+ *       if (name.equals("id")) {
+ *         id = reader.nextLong();
+ *       } else if (name.equals("text")) {
+ *         text = reader.nextString();
+ *       } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
+ *         geo = readDoublesArray(reader);
+ *       } else if (name.equals("user")) {
+ *         user = readUser(reader);
+ *       } else {
+ *         reader.skipValue();
+ *       }
+ *     }
+ *     reader.endObject();
+ *     return new Message(id, text, user, geo);
+ *   }
+ *
+ *   public List<Double> readDoublesArray(JsonReader reader) throws IOException {
+ *     List<Double> doubles = new ArrayList<Double>();
+ *
+ *     reader.beginArray();
+ *     while (reader.hasNext()) {
+ *       doubles.add(reader.nextDouble());
+ *     }
+ *     reader.endArray();
+ *     return doubles;
+ *   }
+ *
+ *   public User readUser(JsonReader reader) throws IOException {
+ *     String username = null;
+ *     int followersCount = -1;
+ *
+ *     reader.beginObject();
+ *     while (reader.hasNext()) {
+ *       String name = reader.nextName();
+ *       if (name.equals("name")) {
+ *         username = reader.nextString();
+ *       } else if (name.equals("followers_count")) {
+ *         followersCount = reader.nextInt();
+ *       } else {
+ *         reader.skipValue();
+ *       }
+ *     }
+ *     reader.endObject();
+ *     return new User(username, followersCount);
+ *   }}</pre>
+ *
+ * <h3>Number Handling</h3>
+ * This reader permits numeric values to be read as strings and string values to
+ * be read as numbers. For example, both elements of the JSON array {@code
+ * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}.
+ * This behavior is intended to prevent lossy numeric conversions: double is
+ * JavaScript's only numeric type and very large values like {@code
+ * 9007199254740993} cannot be represented exactly on that platform. To minimize
+ * precision loss, extremely large values should be written and read as strings
+ * in JSON.
+ *
+ * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
+ * of this class are not thread safe.
+ */
+public final class JsonReader implements Closeable {
+
+    private static final String TRUE = "true";
+    private static final String FALSE = "false";
+
+    private final StringPool stringPool = new StringPool();
+
+    /** The input JSON. */
+    private final Reader in;
+
+    /** True to accept non-spec compliant JSON */
+    private boolean lenient = false;
+
+    /**
+     * Use a manual buffer to easily read and unread upcoming characters, and
+     * also so we can create strings without an intermediate StringBuilder.
+     * We decode literals directly out of this buffer, so it must be at least as
+     * long as the longest token that can be reported as a number.
+     */
+    private final char[] buffer = new char[1024];
+    private int pos = 0;
+    private int limit = 0;
+
+    /*
+     * The offset of the first character in the buffer.
+     */
+    private int bufferStartLine = 1;
+    private int bufferStartColumn = 1;
+
+    private final List<JsonScope> stack = new ArrayList<JsonScope>();
+    {
+        push(JsonScope.EMPTY_DOCUMENT);
+    }
+
+    /**
+     * The type of the next token to be returned by {@link #peek} and {@link
+     * #advance}. If null, peek() will assign a value.
+     */
+    private JsonToken token;
+
+    /** The text of the next name. */
+    private String name;
+
+    /*
+     * For the next literal value, we may have the text value, or the position
+     * and length in the buffer.
+     */
+    private String value;
+    private int valuePos;
+    private int valueLength;
+
+    /** True if we're currently handling a skipValue() call. */
+    private boolean skipping = false;
+
+    /**
+     * Creates a new instance that reads a JSON-encoded stream from {@code in}.
+     */
+    public JsonReader(Reader in) {
+        if (in == null) {
+            throw new NullPointerException("in == null");
+        }
+        this.in = in;
+    }
+
+    /**
+     * Configure this parser to be  be liberal in what it accepts. By default,
+     * this parser is strict and only accepts JSON as specified by <a
+     * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
+     * parser to lenient causes it to ignore the following syntax errors:
+     *
+     * <ul>
+     *   <li>End of line comments starting with {@code //} or {@code #} and
+     *       ending with a newline character.
+     *   <li>C-style comments starting with {@code /*} and ending with
+     *       {@code *}{@code /}. Such comments may not be nested.
+     *   <li>Names that are unquoted or {@code 'single quoted'}.
+     *   <li>Strings that are unquoted or {@code 'single quoted'}.
+     *   <li>Array elements separated by {@code ;} instead of {@code ,}.
+     *   <li>Unnecessary array separators. These are interpreted as if null
+     *       was the omitted value.
+     *   <li>Names and values separated by {@code =} or {@code =>} instead of
+     *       {@code :}.
+     *   <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
+     * </ul>
+     */
+    public void setLenient(boolean lenient) {
+        this.lenient = lenient;
+    }
+
+    /**
+     * Returns true if this parser is liberal in what it accepts.
+     */
+    public boolean isLenient() {
+        return lenient;
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * beginning of a new array.
+     */
+    public void beginArray() throws IOException {
+        expect(JsonToken.BEGIN_ARRAY);
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * end of the current array.
+     */
+    public void endArray() throws IOException {
+        expect(JsonToken.END_ARRAY);
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * beginning of a new object.
+     */
+    public void beginObject() throws IOException {
+        expect(JsonToken.BEGIN_OBJECT);
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is the
+     * end of the current object.
+     */
+    public void endObject() throws IOException {
+        expect(JsonToken.END_OBJECT);
+    }
+
+    /**
+     * Consumes {@code expected}.
+     */
+    private void expect(JsonToken expected) throws IOException {
+        peek();
+        if (token != expected) {
+            throw new IllegalStateException("Expected " + expected + " but was " + peek());
+        }
+        advance();
+    }
+
+    /**
+     * Returns true if the current array or object has another element.
+     */
+    public boolean hasNext() throws IOException {
+        peek();
+        return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
+    }
+
+    /**
+     * Returns the type of the next token without consuming it.
+     */
+    public JsonToken peek() throws IOException {
+        if (token != null) {
+          return token;
+        }
+
+        switch (peekStack()) {
+            case EMPTY_DOCUMENT:
+                replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+                JsonToken firstToken = nextValue();
+                if (!lenient && token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
+                    throw new IOException(
+                            "Expected JSON document to start with '[' or '{' but was " + token);
+                }
+                return firstToken;
+            case EMPTY_ARRAY:
+                return nextInArray(true);
+            case NONEMPTY_ARRAY:
+                return nextInArray(false);
+            case EMPTY_OBJECT:
+                return nextInObject(true);
+            case DANGLING_NAME:
+                return objectValue();
+            case NONEMPTY_OBJECT:
+                return nextInObject(false);
+            case NONEMPTY_DOCUMENT:
+                try {
+                    JsonToken token = nextValue();
+                    if (lenient) {
+                        return token;
+                    }
+                    throw syntaxError("Expected EOF");
+                } catch (EOFException e) {
+                    return token = JsonToken.END_DOCUMENT; // TODO: avoid throwing here?
+                }
+            case CLOSED:
+                throw new IllegalStateException("JsonReader is closed");
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    /**
+     * Advances the cursor in the JSON stream to the next token.
+     */
+    private JsonToken advance() throws IOException {
+        peek();
+
+        JsonToken result = token;
+        token = null;
+        value = null;
+        name = null;
+        return result;
+    }
+
+    /**
+     * Returns the next token, a {@link JsonToken#NAME property name}, and
+     * consumes it.
+     *
+     * @throws IOException if the next token in the stream is not a property
+     *     name.
+     */
+    public String nextName() throws IOException {
+        peek();
+        if (token != JsonToken.NAME) {
+            throw new IllegalStateException("Expected a name but was " + peek());
+        }
+        String result = name;
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#STRING string} value of the next token,
+     * consuming it. If the next token is a number, this method will return its
+     * string form.
+     *
+     * @throws IllegalStateException if the next token is not a string or if
+     *     this reader is closed.
+     */
+    public String nextString() throws IOException {
+        peek();
+        if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+            throw new IllegalStateException("Expected a string but was " + peek());
+        }
+
+        String result = value;
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token,
+     * consuming it.
+     *
+     * @throws IllegalStateException if the next token is not a boolean or if
+     *     this reader is closed.
+     */
+    public boolean nextBoolean() throws IOException {
+        peek();
+        if (token != JsonToken.BOOLEAN) {
+            throw new IllegalStateException("Expected a boolean but was " + token);
+        }
+
+        boolean result = (value == TRUE);
+        advance();
+        return result;
+    }
+
+    /**
+     * Consumes the next token from the JSON stream and asserts that it is a
+     * literal null.
+     *
+     * @throws IllegalStateException if the next token is not null or if this
+     *     reader is closed.
+     */
+    public void nextNull() throws IOException {
+        peek();
+        if (token != JsonToken.NULL) {
+            throw new IllegalStateException("Expected null but was " + token);
+        }
+
+        advance();
+    }
+
+    /**
+     * Returns the {@link JsonToken#NUMBER double} value of the next token,
+     * consuming it. If the next token is a string, this method will attempt to
+     * parse it as a double using {@link Double#parseDouble(String)}.
+     *
+     * @throws IllegalStateException if the next token is not a literal value.
+     */
+    public double nextDouble() throws IOException {
+        peek();
+        if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+            throw new IllegalStateException("Expected a double but was " + token);
+        }
+
+        double result = Double.parseDouble(value);
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#NUMBER long} value of the next token,
+     * consuming it. If the next token is a string, this method will attempt to
+     * parse it as a long. If the next token's numeric value cannot be exactly
+     * represented by a Java {@code long}, this method throws.
+     *
+     * @throws IllegalStateException if the next token is not a literal value.
+     * @throws NumberFormatException if the next literal value cannot be parsed
+     *     as a number, or exactly represented as a long.
+     */
+    public long nextLong() throws IOException {
+        peek();
+        if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+            throw new IllegalStateException("Expected a long but was " + token);
+        }
+
+        long result;
+        try {
+            result = Long.parseLong(value);
+        } catch (NumberFormatException ignored) {
+            double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+            result = (long) asDouble;
+            if ((double) result != asDouble) {
+                throw new NumberFormatException(value);
+            }
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Returns the {@link JsonToken#NUMBER int} value of the next token,
+     * consuming it. If the next token is a string, this method will attempt to
+     * parse it as an int. If the next token's numeric value cannot be exactly
+     * represented by a Java {@code int}, this method throws.
+     *
+     * @throws IllegalStateException if the next token is not a literal value.
+     * @throws NumberFormatException if the next literal value cannot be parsed
+     *     as a number, or exactly represented as an int.
+     */
+    public int nextInt() throws IOException {
+        peek();
+        if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+            throw new IllegalStateException("Expected an int but was " + token);
+        }
+
+        int result;
+        try {
+            result = Integer.parseInt(value);
+        } catch (NumberFormatException ignored) {
+            double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+            result = (int) asDouble;
+            if ((double) result != asDouble) {
+                throw new NumberFormatException(value);
+            }
+        }
+
+        advance();
+        return result;
+    }
+
+    /**
+     * Closes this JSON reader and the underlying {@link Reader}.
+     */
+    public void close() throws IOException {
+        value = null;
+        token = null;
+        stack.clear();
+        stack.add(JsonScope.CLOSED);
+        in.close();
+    }
+
+    /**
+     * Skips the next value recursively. If it is an object or array, all nested
+     * elements are skipped. This method is intended for use when the JSON token
+     * stream contains unrecognized or unhandled values.
+     */
+    public void skipValue() throws IOException {
+        skipping = true;
+        try {
+            if (!hasNext() || peek() == JsonToken.END_DOCUMENT) {
+                throw new IllegalStateException("No element left to skip");
+            }
+            int count = 0;
+            do {
+                JsonToken token = advance();
+                if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) {
+                    count++;
+                } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) {
+                    count--;
+                }
+            } while (count != 0);
+        } finally {
+            skipping = false;
+        }
+    }
+
+    private JsonScope peekStack() {
+        return stack.get(stack.size() - 1);
+    }
+
+    private JsonScope pop() {
+        return stack.remove(stack.size() - 1);
+    }
+
+    private void push(JsonScope newTop) {
+        stack.add(newTop);
+    }
+
+    /**
+     * Replace the value on the top of the stack with the given value.
+     */
+    private void replaceTop(JsonScope newTop) {
+        stack.set(stack.size() - 1, newTop);
+    }
+
+    private JsonToken nextInArray(boolean firstElement) throws IOException {
+        if (firstElement) {
+            replaceTop(JsonScope.NONEMPTY_ARRAY);
+        } else {
+            /* Look for a comma before each element after the first element. */
+            switch (nextNonWhitespace()) {
+                case ']':
+                    pop();
+                    return token = JsonToken.END_ARRAY;
+                case ';':
+                    checkLenient(); // fall-through
+                case ',':
+                    break;
+                default:
+                    throw syntaxError("Unterminated array");
+            }
+        }
+
+        switch (nextNonWhitespace()) {
+            case ']':
+                if (firstElement) {
+                    pop();
+                    return token = JsonToken.END_ARRAY;
+                }
+                // fall-through to handle ",]"
+            case ';':
+            case ',':
+                /* In lenient mode, a 0-length literal means 'null' */
+                checkLenient();
+                pos--;
+                value = "null";
+                return token = JsonToken.NULL;
+            default:
+                pos--;
+                return nextValue();
+        }
+    }
+
+    private JsonToken nextInObject(boolean firstElement) throws IOException {
+        /*
+         * Read delimiters. Either a comma/semicolon separating this and the
+         * previous name-value pair, or a close brace to denote the end of the
+         * object.
+         */
+        if (firstElement) {
+            /* Peek to see if this is the empty object. */
+            switch (nextNonWhitespace()) {
+                case '}':
+                    pop();
+                    return token = JsonToken.END_OBJECT;
+                default:
+                    pos--;
+            }
+        } else {
+            switch (nextNonWhitespace()) {
+                case '}':
+                    pop();
+                    return token = JsonToken.END_OBJECT;
+                case ';':
+                case ',':
+                    break;
+                default:
+                    throw syntaxError("Unterminated object");
+            }
+        }
+
+        /* Read the name. */
+        int quote = nextNonWhitespace();
+        switch (quote) {
+            case '\'':
+                checkLenient(); // fall-through
+            case '"':
+                name = nextString((char) quote);
+                break;
+            default:
+                checkLenient();
+                pos--;
+                name = nextLiteral(false);
+                if (name.isEmpty()) {
+                    throw syntaxError("Expected name");
+                }
+        }
+
+        replaceTop(JsonScope.DANGLING_NAME);
+        return token = JsonToken.NAME;
+    }
+
+    private JsonToken objectValue() throws IOException {
+        /*
+         * Read the name/value separator. Usually a colon ':'. In lenient mode
+         * we also accept an equals sign '=', or an arrow "=>".
+         */
+        switch (nextNonWhitespace()) {
+            case ':':
+                break;
+            case '=':
+                checkLenient();
+                if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
+                    pos++;
+                }
+                break;
+            default:
+                throw syntaxError("Expected ':'");
+        }
+
+        replaceTop(JsonScope.NONEMPTY_OBJECT);
+        return nextValue();
+    }
+
+    private JsonToken nextValue() throws IOException {
+        int c = nextNonWhitespace();
+        switch (c) {
+            case '{':
+                push(JsonScope.EMPTY_OBJECT);
+                return token = JsonToken.BEGIN_OBJECT;
+
+            case '[':
+                push(JsonScope.EMPTY_ARRAY);
+                return token = JsonToken.BEGIN_ARRAY;
+
+            case '\'':
+                checkLenient(); // fall-through
+            case '"':
+                value = nextString((char) c);
+                return token = JsonToken.STRING;
+
+            default:
+                pos--;
+                return readLiteral();
+        }
+    }
+
+    /**
+     * Returns true once {@code limit - pos >= minimum}. If the data is
+     * exhausted before that many characters are available, this returns
+     * false.
+     */
+    private boolean fillBuffer(int minimum) throws IOException {
+        // Before clobbering the old characters, update where buffer starts
+        for (int i = 0; i < pos; i++) {
+            if (buffer[i] == '\n') {
+                bufferStartLine++;
+                bufferStartColumn = 1;
+            } else {
+                bufferStartColumn++;
+            }
+        }
+
+        if (limit != pos) {
+            limit -= pos;
+            System.arraycopy(buffer, pos, buffer, 0, limit);
+        } else {
+            limit = 0;
+        }
+
+        pos = 0;
+        int total;
+        while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
+            limit += total;
+
+            // if this is the first read, consume an optional byte order mark (BOM) if it exists
+                if (bufferStartLine == 1 && bufferStartColumn == 1
+                        && limit > 0 && buffer[0] == '\ufeff') {
+                pos++;
+                bufferStartColumn--;
+            }
+
+            if (limit >= minimum) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int getLineNumber() {
+        int result = bufferStartLine;
+        for (int i = 0; i < pos; i++) {
+            if (buffer[i] == '\n') {
+                result++;
+            }
+        }
+        return result;
+    }
+
+    private int getColumnNumber() {
+        int result = bufferStartColumn;
+        for (int i = 0; i < pos; i++) {
+            if (buffer[i] == '\n') {
+                result = 1;
+            } else {
+                result++;
+            }
+        }
+        return result;
+    }
+
+    private int nextNonWhitespace() throws IOException {
+        while (pos < limit || fillBuffer(1)) {
+            int c = buffer[pos++];
+            switch (c) {
+                case '\t':
+                case ' ':
+                case '\n':
+                case '\r':
+                    continue;
+
+                case '/':
+                    if (pos == limit && !fillBuffer(1)) {
+                        return c;
+                    }
+
+                    checkLenient();
+                    char peek = buffer[pos];
+                    switch (peek) {
+                        case '*':
+                            // skip a /* c-style comment */
+                            pos++;
+                            if (!skipTo("*/")) {
+                                throw syntaxError("Unterminated comment");
+                            }
+                            pos += 2;
+                            continue;
+
+                        case '/':
+                            // skip a // end-of-line comment
+                            pos++;
+                            skipToEndOfLine();
+                            continue;
+
+                        default:
+                            return c;
+                    }
+
+                case '#':
+                    /*
+                     * Skip a # hash end-of-line comment. The JSON RFC doesn't
+                     * specify this behaviour, but it's required to parse
+                     * existing documents. See http://b/2571423.
+                     */
+                    checkLenient();
+                    skipToEndOfLine();
+                    continue;
+
+                default:
+                    return c;
+            }
+        }
+
+        throw new EOFException("End of input");
+    }
+
+    private void checkLenient() throws IOException {
+        if (!lenient) {
+            throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
+        }
+    }
+
+    /**
+     * Advances the position until after the next newline character. If the line
+     * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
+     * caller.
+     */
+    private void skipToEndOfLine() throws IOException {
+        while (pos < limit || fillBuffer(1)) {
+            char c = buffer[pos++];
+            if (c == '\r' || c == '\n') {
+                break;
+            }
+        }
+    }
+
+    private boolean skipTo(String toFind) throws IOException {
+        outer:
+        for (; pos + toFind.length() <= limit || fillBuffer(toFind.length()); pos++) {
+            for (int c = 0; c < toFind.length(); c++) {
+                if (buffer[pos + c] != toFind.charAt(c)) {
+                    continue outer;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the string up to but not including {@code quote}, unescaping any
+     * character escape sequences encountered along the way. The opening quote
+     * should have already been read. This consumes the closing quote, but does
+     * not include it in the returned string.
+     *
+     * @param quote either ' or ".
+     * @throws NumberFormatException if any unicode escape sequences are
+     *     malformed.
+     */
+    private String nextString(char quote) throws IOException {
+        StringBuilder builder = null;
+        do {
+            /* the index of the first character not yet appended to the builder. */
+            int start = pos;
+            while (pos < limit) {
+                int c = buffer[pos++];
+
+                if (c == quote) {
+                    if (skipping) {
+                        return "skipped!";
+                    } else if (builder == null) {
+                        return stringPool.get(buffer, start, pos - start - 1);
+                    } else {
+                        builder.append(buffer, start, pos - start - 1);
+                        return builder.toString();
+                    }
+
+                } else if (c == '\\') {
+                    if (builder == null) {
+                        builder = new StringBuilder();
+                    }
+                    builder.append(buffer, start, pos - start - 1);
+                    builder.append(readEscapeCharacter());
+                    start = pos;
+                }
+            }
+
+            if (builder == null) {
+                builder = new StringBuilder();
+            }
+            builder.append(buffer, start, pos - start);
+        } while (fillBuffer(1));
+
+        throw syntaxError("Unterminated string");
+    }
+
+    /**
+     * Reads the value up to but not including any delimiter characters. This
+     * does not consume the delimiter character.
+     *
+     * @param assignOffsetsOnly true for this method to only set the valuePos
+     *     and valueLength fields and return a null result. This only works if
+     *     the literal is short; a string is returned otherwise.
+     */
+    private String nextLiteral(boolean assignOffsetsOnly) throws IOException {
+        StringBuilder builder = null;
+        valuePos = -1;
+        valueLength = 0;
+        int i = 0;
+
+        findNonLiteralCharacter:
+        while (true) {
+            for (; pos + i < limit; i++) {
+                switch (buffer[pos + i]) {
+                case '/':
+                case '\\':
+                case ';':
+                case '#':
+                case '=':
+                    checkLenient(); // fall-through
+                case '{':
+                case '}':
+                case '[':
+                case ']':
+                case ':':
+                case ',':
+                case ' ':
+                case '\t':
+                case '\f':
+                case '\r':
+                case '\n':
+                    break findNonLiteralCharacter;
+                }
+            }
+
+            /*
+             * Attempt to load the entire literal into the buffer at once. If
+             * we run out of input, add a non-literal character at the end so
+             * that decoding doesn't need to do bounds checks.
+             */
+            if (i < buffer.length) {
+                if (fillBuffer(i + 1)) {
+                    continue;
+                } else {
+                    buffer[limit] = '\0';
+                    break;
+                }
+            }
+
+            // use a StringBuilder when the value is too long. It must be an unquoted string.
+            if (builder == null) {
+                builder = new StringBuilder();
+            }
+            builder.append(buffer, pos, i);
+            valueLength += i;
+            pos += i;
+            i = 0;
+            if (!fillBuffer(1)) {
+                break;
+            }
+        }
+
+        String result;
+        if (assignOffsetsOnly && builder == null) {
+            valuePos = pos;
+            result = null;
+        } else if (skipping) {
+            result = "skipped!";
+        } else if (builder == null) {
+            result = stringPool.get(buffer, pos, i);
+        } else {
+            builder.append(buffer, pos, i);
+            result = builder.toString();
+        }
+        valueLength += i;
+        pos += i;
+        return result;
+    }
+
+    @Override public String toString() {
+        return getClass().getSimpleName() + " near " + getSnippet();
+    }
+
+    /**
+     * Unescapes the character identified by the character or characters that
+     * immediately follow a backslash. The backslash '\' should have already
+     * been read. This supports both unicode escapes "u000A" and two-character
+     * escapes "\n".
+     *
+     * @throws NumberFormatException if any unicode escape sequences are
+     *     malformed.
+     */
+    private char readEscapeCharacter() throws IOException {
+        if (pos == limit && !fillBuffer(1)) {
+            throw syntaxError("Unterminated escape sequence");
+        }
+
+        char escaped = buffer[pos++];
+        switch (escaped) {
+            case 'u':
+                if (pos + 4 > limit && !fillBuffer(4)) {
+                    throw syntaxError("Unterminated escape sequence");
+                }
+                String hex = stringPool.get(buffer, pos, 4);
+                pos += 4;
+                return (char) Integer.parseInt(hex, 16);
+
+            case 't':
+                return '\t';
+
+            case 'b':
+                return '\b';
+
+            case 'n':
+                return '\n';
+
+            case 'r':
+                return '\r';
+
+            case 'f':
+                return '\f';
+
+            case '\'':
+            case '"':
+            case '\\':
+            default:
+                return escaped;
+        }
+    }
+
+    /**
+     * Reads a null, boolean, numeric or unquoted string literal value.
+     */
+    private JsonToken readLiteral() throws IOException {
+        value = nextLiteral(true);
+        if (valueLength == 0) {
+            throw syntaxError("Expected literal value");
+        }
+        token = decodeLiteral();
+        if (token == JsonToken.STRING) {
+          checkLenient();
+        }
+        return token;
+    }
+
+    /**
+     * Assigns {@code nextToken} based on the value of {@code nextValue}.
+     */
+    private JsonToken decodeLiteral() throws IOException {
+        if (valuePos == -1) {
+            // it was too long to fit in the buffer so it can only be a string
+            return JsonToken.STRING;
+        } else if (valueLength == 4
+                && ('n' == buffer[valuePos    ] || 'N' == buffer[valuePos    ])
+                && ('u' == buffer[valuePos + 1] || 'U' == buffer[valuePos + 1])
+                && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2])
+                && ('l' == buffer[valuePos + 3] || 'L' == buffer[valuePos + 3])) {
+            value = "null";
+            return JsonToken.NULL;
+        } else if (valueLength == 4
+                && ('t' == buffer[valuePos    ] || 'T' == buffer[valuePos    ])
+                && ('r' == buffer[valuePos + 1] || 'R' == buffer[valuePos + 1])
+                && ('u' == buffer[valuePos + 2] || 'U' == buffer[valuePos + 2])
+                && ('e' == buffer[valuePos + 3] || 'E' == buffer[valuePos + 3])) {
+            value = TRUE;
+            return JsonToken.BOOLEAN;
+        } else if (valueLength == 5
+                && ('f' == buffer[valuePos    ] || 'F' == buffer[valuePos    ])
+                && ('a' == buffer[valuePos + 1] || 'A' == buffer[valuePos + 1])
+                && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2])
+                && ('s' == buffer[valuePos + 3] || 'S' == buffer[valuePos + 3])
+                && ('e' == buffer[valuePos + 4] || 'E' == buffer[valuePos + 4])) {
+            value = FALSE;
+            return JsonToken.BOOLEAN;
+        } else {
+            value = stringPool.get(buffer, valuePos, valueLength);
+            return decodeNumber(buffer, valuePos, valueLength);
+        }
+    }
+
+    /**
+     * Determine whether the characters is a JSON number. Numbers are of the
+     * form -12.34e+56. Fractional and exponential parts are optional. Leading
+     * zeroes are not allowed in the value or exponential part, but are allowed
+     * in the fraction.
+     */
+    private JsonToken decodeNumber(char[] chars, int offset, int length) {
+        int i = offset;
+        int c = chars[i];
+
+        if (c == '-') {
+            c = chars[++i];
+        }
+
+        if (c == '0') {
+            c = chars[++i];
+        } else if (c >= '1' && c <= '9') {
+            c = chars[++i];
+            while (c >= '0' && c <= '9') {
+                c = chars[++i];
+            }
+        } else {
+            return JsonToken.STRING;
+        }
+
+        if (c == '.') {
+            c = chars[++i];
+            while (c >= '0' && c <= '9') {
+                c = chars[++i];
+            }
+        }
+
+        if (c == 'e' || c == 'E') {
+            c = chars[++i];
+            if (c == '+' || c == '-') {
+                c = chars[++i];
+            }
+            if (c >= '0' && c <= '9') {
+                c = chars[++i];
+                while (c >= '0' && c <= '9') {
+                    c = chars[++i];
+                }
+            } else {
+                return JsonToken.STRING;
+            }
+        }
+
+        if (i == offset + length) {
+            return JsonToken.NUMBER;
+        } else {
+            return JsonToken.STRING;
+        }
+    }
+
+    /**
+     * Throws a new IO exception with the given message and a context snippet
+     * with this reader's content.
+     */
+    private IOException syntaxError(String message) throws IOException {
+        throw new MalformedJsonException(message
+                + " at line " + getLineNumber() + " column " + getColumnNumber());
+    }
+
+    private CharSequence getSnippet() {
+        StringBuilder snippet = new StringBuilder();
+        int beforePos = Math.min(pos, 20);
+        snippet.append(buffer, pos - beforePos, beforePos);
+        int afterPos = Math.min(limit - pos, 20);
+        snippet.append(buffer, pos, afterPos);
+        return snippet;
+    }
+}
diff --git a/android/util/JsonScope.java b/android/util/JsonScope.java
new file mode 100644
index 0000000..ca534e9
--- /dev/null
+++ b/android/util/JsonScope.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 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;
+
+/**
+ * Lexical scoping elements within a JSON reader or writer.
+ */
+enum JsonScope {
+
+    /**
+     * An array with no elements requires no separators or newlines before
+     * it is closed.
+     */
+    EMPTY_ARRAY,
+
+    /**
+     * A array with at least one value requires a comma and newline before
+     * the next element.
+     */
+    NONEMPTY_ARRAY,
+
+    /**
+     * An object with no name/value pairs requires no separators or newlines
+     * before it is closed.
+     */
+    EMPTY_OBJECT,
+
+    /**
+     * An object whose most recent element is a key. The next element must
+     * be a value.
+     */
+    DANGLING_NAME,
+
+    /**
+     * An object with at least one name/value pair requires a comma and
+     * newline before the next element.
+     */
+    NONEMPTY_OBJECT,
+
+    /**
+     * No object or array has been started.
+     */
+    EMPTY_DOCUMENT,
+
+    /**
+     * A document with at an array or object.
+     */
+    NONEMPTY_DOCUMENT,
+
+    /**
+     * A document that's been closed and cannot be accessed.
+     */
+    CLOSED,
+}
diff --git a/android/util/JsonToken.java b/android/util/JsonToken.java
new file mode 100644
index 0000000..45bc6ca
--- /dev/null
+++ b/android/util/JsonToken.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 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;
+
+/**
+ * A structure, name or value type in a JSON-encoded string.
+ */
+public enum JsonToken {
+
+    /**
+     * The opening of a JSON array. Written using {@link JsonWriter#beginObject}
+     * and read using {@link JsonReader#beginObject}.
+     */
+    BEGIN_ARRAY,
+
+    /**
+     * The closing of a JSON array. Written using {@link JsonWriter#endArray}
+     * and read using {@link JsonReader#endArray}.
+     */
+    END_ARRAY,
+
+    /**
+     * The opening of a JSON object. Written using {@link JsonWriter#beginObject}
+     * and read using {@link JsonReader#beginObject}.
+     */
+    BEGIN_OBJECT,
+
+    /**
+     * The closing of a JSON object. Written using {@link JsonWriter#endObject}
+     * and read using {@link JsonReader#endObject}.
+     */
+    END_OBJECT,
+
+    /**
+     * A JSON property name. Within objects, tokens alternate between names and
+     * their values. Written using {@link JsonWriter#name} and read using {@link
+     * JsonReader#nextName}
+     */
+    NAME,
+
+    /**
+     * A JSON string.
+     */
+    STRING,
+
+    /**
+     * A JSON number represented in this API by a Java {@code double}, {@code
+     * long}, or {@code int}.
+     */
+    NUMBER,
+
+    /**
+     * A JSON {@code true} or {@code false}.
+     */
+    BOOLEAN,
+
+    /**
+     * A JSON {@code null}.
+     */
+    NULL,
+
+    /**
+     * The end of the JSON stream. This sentinel value is returned by {@link
+     * JsonReader#peek()} to signal that the JSON-encoded value has no more
+     * tokens.
+     */
+    END_DOCUMENT
+}
diff --git a/android/util/JsonWriter.java b/android/util/JsonWriter.java
new file mode 100644
index 0000000..c1e6e40
--- /dev/null
+++ b/android/util/JsonWriter.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2010 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;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value to a stream, one token at a time. The stream includes both
+ * literal values (strings, numbers, booleans and nulls) as well as the begin
+ * and end delimiters of objects and arrays.
+ *
+ * <h3>Encoding JSON</h3>
+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
+ * document must contain one top-level array or object. Call methods on the
+ * writer as you walk the structure's contents, nesting arrays and objects as
+ * necessary:
+ * <ul>
+ *   <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
+ *       Write each of the array's elements with the appropriate {@link #value}
+ *       methods or by nesting other arrays and objects. Finally close the array
+ *       using {@link #endArray()}.
+ *   <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
+ *       Write each of the object's properties by alternating calls to
+ *       {@link #name} with the property's value. Write property values with the
+ *       appropriate {@link #value} method or by nesting other objects or arrays.
+ *       Finally close the object using {@link #endObject()}.
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
+ * [
+ *   {
+ *     "id": 912345678901,
+ *     "text": "How do I write JSON on Android?",
+ *     "geo": null,
+ *     "user": {
+ *       "name": "android_newb",
+ *       "followers_count": 41
+ *      }
+ *   },
+ *   {
+ *     "id": 912345678902,
+ *     "text": "@android_newb just use android.util.JsonWriter!",
+ *     "geo": [50.454722, -104.606667],
+ *     "user": {
+ *       "name": "jesse",
+ *       "followers_count": 2
+ *     }
+ *   }
+ * ]}</pre>
+ * This code encodes the above structure: <pre>   {@code
+ *   public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
+ *     JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ *     writer.setIndent("  ");
+ *     writeMessagesArray(writer, messages);
+ *     writer.close();
+ *   }
+ *
+ *   public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
+ *     writer.beginArray();
+ *     for (Message message : messages) {
+ *       writeMessage(writer, message);
+ *     }
+ *     writer.endArray();
+ *   }
+ *
+ *   public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ *     writer.beginObject();
+ *     writer.name("id").value(message.getId());
+ *     writer.name("text").value(message.getText());
+ *     if (message.getGeo() != null) {
+ *       writer.name("geo");
+ *       writeDoublesArray(writer, message.getGeo());
+ *     } else {
+ *       writer.name("geo").nullValue();
+ *     }
+ *     writer.name("user");
+ *     writeUser(writer, message.getUser());
+ *     writer.endObject();
+ *   }
+ *
+ *   public void writeUser(JsonWriter writer, User user) throws IOException {
+ *     writer.beginObject();
+ *     writer.name("name").value(user.getName());
+ *     writer.name("followers_count").value(user.getFollowersCount());
+ *     writer.endObject();
+ *   }
+ *
+ *   public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
+ *     writer.beginArray();
+ *     for (Double value : doubles) {
+ *       writer.value(value);
+ *     }
+ *     writer.endArray();
+ *   }}</pre>
+ *
+ * <p>Each {@code JsonWriter} may be used to write a single JSON stream.
+ * Instances of this class are not thread safe. Calls that would result in a
+ * malformed JSON string will fail with an {@link IllegalStateException}.
+ */
+public final class JsonWriter implements Closeable {
+
+    /** The output data, containing at most one top-level array or object. */
+    private final Writer out;
+
+    private final List<JsonScope> stack = new ArrayList<JsonScope>();
+    {
+        stack.add(JsonScope.EMPTY_DOCUMENT);
+    }
+
+    /**
+     * A string containing a full set of spaces for a single level of
+     * indentation, or null for no pretty printing.
+     */
+    private String indent;
+
+    /**
+     * The name/value separator; either ":" or ": ".
+     */
+    private String separator = ":";
+
+    private boolean lenient;
+
+    /**
+     * Creates a new instance that writes a JSON-encoded stream to {@code out}.
+     * For best performance, ensure {@link Writer} is buffered; wrapping in
+     * {@link java.io.BufferedWriter BufferedWriter} if necessary.
+     */
+    public JsonWriter(Writer out) {
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+        this.out = out;
+    }
+
+    /**
+     * Sets the indentation string to be repeated for each level of indentation
+     * in the encoded document. If {@code indent.isEmpty()} the encoded document
+     * will be compact. Otherwise the encoded document will be more
+     * human-readable.
+     *
+     * @param indent a string containing only whitespace.
+     */
+    public void setIndent(String indent) {
+        if (indent.isEmpty()) {
+            this.indent = null;
+            this.separator = ":";
+        } else {
+            this.indent = indent;
+            this.separator = ": ";
+        }
+    }
+
+    /**
+     * Configure this writer to relax its syntax rules. By default, this writer
+     * only emits well-formed JSON as specified by <a
+     * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the writer
+     * to lenient permits the following:
+     * <ul>
+     *   <li>Top-level values of any type. With strict writing, the top-level
+     *       value must be an object or an array.
+     *   <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
+     *       Double#isInfinite() infinities}.
+     * </ul>
+     */
+    public void setLenient(boolean lenient) {
+        this.lenient = lenient;
+    }
+
+    /**
+     * Returns true if this writer has relaxed syntax rules.
+     */
+    public boolean isLenient() {
+        return lenient;
+    }
+
+    /**
+     * Begins encoding a new array. Each call to this method must be paired with
+     * a call to {@link #endArray}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter beginArray() throws IOException {
+        return open(JsonScope.EMPTY_ARRAY, "[");
+    }
+
+    /**
+     * Ends encoding the current array.
+     *
+     * @return this writer.
+     */
+    public JsonWriter endArray() throws IOException {
+        return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]");
+    }
+
+    /**
+     * Begins encoding a new object. Each call to this method must be paired
+     * with a call to {@link #endObject}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter beginObject() throws IOException {
+        return open(JsonScope.EMPTY_OBJECT, "{");
+    }
+
+    /**
+     * Ends encoding the current object.
+     *
+     * @return this writer.
+     */
+    public JsonWriter endObject() throws IOException {
+        return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}");
+    }
+
+    /**
+     * Enters a new scope by appending any necessary whitespace and the given
+     * bracket.
+     */
+    private JsonWriter open(JsonScope empty, String openBracket) throws IOException {
+        beforeValue(true);
+        stack.add(empty);
+        out.write(openBracket);
+        return this;
+    }
+
+    /**
+     * Closes the current scope by appending any necessary whitespace and the
+     * given bracket.
+     */
+    private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket)
+            throws IOException {
+        JsonScope context = peek();
+        if (context != nonempty && context != empty) {
+            throw new IllegalStateException("Nesting problem: " + stack);
+        }
+
+        stack.remove(stack.size() - 1);
+        if (context == nonempty) {
+            newline();
+        }
+        out.write(closeBracket);
+        return this;
+    }
+
+    /**
+     * Returns the value on the top of the stack.
+     */
+    private JsonScope peek() {
+        return stack.get(stack.size() - 1);
+    }
+
+    /**
+     * Replace the value on the top of the stack with the given value.
+     */
+    private void replaceTop(JsonScope topOfStack) {
+        stack.set(stack.size() - 1, topOfStack);
+    }
+
+    /**
+     * Encodes the property name.
+     *
+     * @param name the name of the forthcoming value. May not be null.
+     * @return this writer.
+     */
+    public JsonWriter name(String name) throws IOException {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+        beforeName();
+        string(name);
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @param value the literal string value, or null to encode a null literal.
+     * @return this writer.
+     */
+    public JsonWriter value(String value) throws IOException {
+        if (value == null) {
+            return nullValue();
+        }
+        beforeValue(false);
+        string(value);
+        return this;
+    }
+
+    /**
+     * Encodes {@code null}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter nullValue() throws IOException {
+        beforeValue(false);
+        out.write("null");
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter value(boolean value) throws IOException {
+        beforeValue(false);
+        out.write(value ? "true" : "false");
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities} unless this writer is lenient.
+     * @return this writer.
+     */
+    public JsonWriter value(double value) throws IOException {
+        if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) {
+            throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+        }
+        beforeValue(false);
+        out.append(Double.toString(value));
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @return this writer.
+     */
+    public JsonWriter value(long value) throws IOException {
+        beforeValue(false);
+        out.write(Long.toString(value));
+        return this;
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities} unless this writer is lenient.
+     * @return this writer.
+     */
+    public JsonWriter value(Number value) throws IOException {
+        if (value == null) {
+            return nullValue();
+        }
+
+        String string = value.toString();
+        if (!lenient &&
+                (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
+            throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+        }
+        beforeValue(false);
+        out.append(string);
+        return this;
+    }
+
+    /**
+     * Ensures all buffered data is written to the underlying {@link Writer}
+     * and flushes that writer.
+     */
+    public void flush() throws IOException {
+        out.flush();
+    }
+
+    /**
+     * Flushes and closes this writer and the underlying {@link Writer}.
+     *
+     * @throws IOException if the JSON document is incomplete.
+     */
+    public void close() throws IOException {
+        out.close();
+
+        if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
+            throw new IOException("Incomplete document");
+        }
+    }
+
+    private void string(String value) throws IOException {
+        out.write("\"");
+        for (int i = 0, length = value.length(); i < length; i++) {
+            char c = value.charAt(i);
+
+            /*
+             * From RFC 4627, "All Unicode characters may be placed within the
+             * quotation marks except for the characters that must be escaped:
+             * quotation mark, reverse solidus, and the control characters
+             * (U+0000 through U+001F)."
+             *
+             * We also escape '\u2028' and '\u2029', which JavaScript interprets
+             * as newline characters. This prevents eval() from failing with a
+             * syntax error.
+             * http://code.google.com/p/google-gson/issues/detail?id=341
+             */
+            switch (c) {
+                case '"':
+                case '\\':
+                    out.write('\\');
+                    out.write(c);
+                    break;
+
+                case '\t':
+                    out.write("\\t");
+                    break;
+
+                case '\b':
+                    out.write("\\b");
+                    break;
+
+                case '\n':
+                    out.write("\\n");
+                    break;
+
+                case '\r':
+                    out.write("\\r");
+                    break;
+
+                case '\f':
+                    out.write("\\f");
+                    break;
+
+                case '\u2028':
+                case '\u2029':
+                    out.write(String.format("\\u%04x", (int) c));
+                    break;
+
+                default:
+                    if (c <= 0x1F) {
+                        out.write(String.format("\\u%04x", (int) c));
+                    } else {
+                        out.write(c);
+                    }
+                    break;
+            }
+
+        }
+        out.write("\"");
+    }
+
+    private void newline() throws IOException {
+        if (indent == null) {
+            return;
+        }
+
+        out.write("\n");
+        for (int i = 1; i < stack.size(); i++) {
+            out.write(indent);
+        }
+    }
+
+    /**
+     * Inserts any necessary separators and whitespace before a name. Also
+     * adjusts the stack to expect the name's value.
+     */
+    private void beforeName() throws IOException {
+        JsonScope context = peek();
+        if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
+            out.write(',');
+        } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
+            throw new IllegalStateException("Nesting problem: " + stack);
+        }
+        newline();
+        replaceTop(JsonScope.DANGLING_NAME);
+    }
+
+    /**
+     * Inserts any necessary separators and whitespace before a literal value,
+     * inline array, or inline object. Also adjusts the stack to expect either a
+     * closing bracket or another element.
+     *
+     * @param root true if the value is a new array or object, the two values
+     *     permitted as top-level elements.
+     */
+    private void beforeValue(boolean root) throws IOException {
+        switch (peek()) {
+            case EMPTY_DOCUMENT: // first in document
+                if (!lenient && !root) {
+                    throw new IllegalStateException(
+                            "JSON must start with an array or an object.");
+                }
+                replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+                break;
+
+            case EMPTY_ARRAY: // first in array
+                replaceTop(JsonScope.NONEMPTY_ARRAY);
+                newline();
+                break;
+
+            case NONEMPTY_ARRAY: // another in array
+                out.append(',');
+                newline();
+                break;
+
+            case DANGLING_NAME: // value for name
+                out.append(separator);
+                replaceTop(JsonScope.NONEMPTY_OBJECT);
+                break;
+
+            case NONEMPTY_DOCUMENT:
+                throw new IllegalStateException(
+                        "JSON must have only one top-level value.");
+
+            default:
+                throw new IllegalStateException("Nesting problem: " + stack);
+        }
+    }
+}
diff --git a/android/util/KeyValueListParser.java b/android/util/KeyValueListParser.java
new file mode 100644
index 0000000..fbc66e6
--- /dev/null
+++ b/android/util/KeyValueListParser.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2015 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;
+
+import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.format.DateTimeParseException;
+
+/**
+ * Parses a list of key=value pairs, separated by some delimiter, and puts the results in
+ * an internal Map. Values can be then queried by key, or if not found, a default value
+ * can be used.
+ * @hide
+ */
+public class KeyValueListParser {
+    private final ArrayMap<String, String> mValues = new ArrayMap<>();
+    private final TextUtils.StringSplitter mSplitter;
+
+    /**
+     * Constructs a new KeyValueListParser. This can be reused for different strings
+     * by calling {@link #setString(String)}.
+     * @param delim The delimiter that separates key=value pairs.
+     */
+    public KeyValueListParser(char delim) {
+        mSplitter = new TextUtils.SimpleStringSplitter(delim);
+    }
+
+    /**
+     * Resets the parser with a new string to parse. The string is expected to be in the following
+     * format:
+     * <pre>key1=value,key2=value,key3=value</pre>
+     *
+     * where the delimiter is a comma.
+     *
+     * @param str the string to parse.
+     * @throws IllegalArgumentException if the string is malformed.
+     */
+    public void setString(String str) throws IllegalArgumentException {
+        mValues.clear();
+        if (str != null) {
+            mSplitter.setString(str);
+            for (String pair : mSplitter) {
+                int sep = pair.indexOf('=');
+                if (sep < 0) {
+                    mValues.clear();
+                    throw new IllegalArgumentException(
+                            "'" + pair + "' in '" + str + "' is not a valid key-value pair");
+                }
+                mValues.put(pair.substring(0, sep).trim(), pair.substring(sep + 1).trim());
+            }
+        }
+    }
+
+    /**
+     * Get the value for key as an int.
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found, or the value was not a long.
+     * @return the int value associated with the key.
+     */
+    public int getInt(String key, int def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                return Integer.parseInt(value);
+            } catch (NumberFormatException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
+
+    /**
+     * Get the value for key as a long.
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found, or the value was not a long.
+     * @return the long value associated with the key.
+     */
+    public long getLong(String key, long def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                return Long.parseLong(value);
+            } catch (NumberFormatException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
+
+    /**
+     * Get the value for key as a float.
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found, or the value was not a float.
+     * @return the float value associated with the key.
+     */
+    public float getFloat(String key, float def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                return Float.parseFloat(value);
+            } catch (NumberFormatException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
+
+    /**
+     * Get the value for key as a string.
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found.
+     * @return the string value associated with the key.
+     */
+    public String getString(String key, String def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            return value;
+        }
+        return def;
+    }
+
+    /**
+     * Get the value for key as a boolean.
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found.
+     * @return the string value associated with the key.
+     */
+    public boolean getBoolean(String key, boolean def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                return Boolean.parseBoolean(value);
+            } catch (NumberFormatException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
+
+    /**
+     * Get the value for key as an integer array.
+     *
+     * The value should be encoded as "0:1:2:3:4"
+     *
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found.
+     * @return the int[] value associated with the key.
+     */
+    public int[] getIntArray(String key, int[] def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                String[] parts = value.split(":");
+                if (parts.length > 0) {
+                    int[] ret = new int[parts.length];
+                    for (int i = 0; i < parts.length; i++) {
+                        ret[i] = Integer.parseInt(parts[i]);
+                    }
+                    return ret;
+                }
+            } catch (NumberFormatException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
+
+    /**
+     * @return the number of keys.
+     */
+    public int size() {
+        return mValues.size();
+    }
+
+    /**
+     * @return the key at {@code index}. Use with {@link #size()} to enumerate all key-value pairs.
+     */
+    public String keyAt(int index) {
+        return mValues.keyAt(index);
+    }
+
+    /**
+     * {@hide}
+     * Parse a duration in millis based on java.time.Duration or just a number (millis)
+     */
+    public long getDurationMillis(String key, long def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                if (value.startsWith("P") || value.startsWith("p")) {
+                    return Duration.parse(value).toMillis();
+                } else {
+                    return Long.parseLong(value);
+                }
+            } catch (NumberFormatException | DateTimeParseException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
+
+    /** Represents an integer config value. */
+    public static class IntValue {
+        private final String mKey;
+        private final int mDefaultValue;
+        private int mValue;
+
+        /** Constructor, initialize with a config key and a default value. */
+        public IntValue(String key, int defaultValue) {
+            mKey = key;
+            mDefaultValue = defaultValue;
+            mValue = mDefaultValue;
+        }
+
+        /** Read a value from {@link KeyValueListParser} */
+        public void parse(KeyValueListParser parser) {
+            mValue = parser.getInt(mKey, mDefaultValue);
+        }
+
+        /** Return the config key. */
+        public String getKey() {
+            return mKey;
+        }
+
+        /** Return the default value. */
+        public int getDefaultValue() {
+            return mDefaultValue;
+        }
+
+        /** Return the actual config value. */
+        public int getValue() {
+            return mValue;
+        }
+
+        /** Overwrites with a value. */
+        public void setValue(int value) {
+            mValue = value;
+        }
+
+        /** Used for dumpsys */
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.print(mKey);
+            pw.print("=");
+            pw.print(mValue);
+            pw.println();
+        }
+
+        /** Used for proto dumpsys */
+        public void dumpProto(ProtoOutputStream proto, long tag) {
+            proto.write(tag, mValue);
+        }
+    }
+
+    /** Represents an long config value. */
+    public static class LongValue {
+        private final String mKey;
+        private final long mDefaultValue;
+        private long mValue;
+
+        /** Constructor, initialize with a config key and a default value. */
+        public LongValue(String key, long defaultValue) {
+            mKey = key;
+            mDefaultValue = defaultValue;
+            mValue = mDefaultValue;
+        }
+
+        /** Read a value from {@link KeyValueListParser} */
+        public void parse(KeyValueListParser parser) {
+            mValue = parser.getLong(mKey, mDefaultValue);
+        }
+
+        /** Return the config key. */
+        public String getKey() {
+            return mKey;
+        }
+
+        /** Return the default value. */
+        public long getDefaultValue() {
+            return mDefaultValue;
+        }
+
+        /** Return the actual config value. */
+        public long getValue() {
+            return mValue;
+        }
+
+        /** Overwrites with a value. */
+        public void setValue(long value) {
+            mValue = value;
+        }
+
+        /** Used for dumpsys */
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.print(mKey);
+            pw.print("=");
+            pw.print(mValue);
+            pw.println();
+        }
+
+        /** Used for proto dumpsys */
+        public void dumpProto(ProtoOutputStream proto, long tag) {
+            proto.write(tag, mValue);
+        }
+    }
+
+    /** Represents an string config value. */
+    public static class StringValue {
+        private final String mKey;
+        private final String mDefaultValue;
+        private String mValue;
+
+        /** Constructor, initialize with a config key and a default value. */
+        public StringValue(String key, String defaultValue) {
+            mKey = key;
+            mDefaultValue = defaultValue;
+            mValue = mDefaultValue;
+        }
+
+        /** Read a value from {@link KeyValueListParser} */
+        public void parse(KeyValueListParser parser) {
+            mValue = parser.getString(mKey, mDefaultValue);
+        }
+
+        /** Return the config key. */
+        public String getKey() {
+            return mKey;
+        }
+
+        /** Return the default value. */
+        public String getDefaultValue() {
+            return mDefaultValue;
+        }
+
+        /** Return the actual config value. */
+        public String getValue() {
+            return mValue;
+        }
+
+        /** Overwrites with a value. */
+        public void setValue(String value) {
+            mValue = value;
+        }
+
+        /** Used for dumpsys */
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.print(mKey);
+            pw.print("=");
+            pw.print(mValue);
+            pw.println();
+        }
+
+        /** Used for proto dumpsys */
+        public void dumpProto(ProtoOutputStream proto, long tag) {
+            proto.write(tag, mValue);
+        }
+    }
+
+    /** Represents an float config value. */
+    public static class FloatValue {
+        private final String mKey;
+        private final float mDefaultValue;
+        private float mValue;
+
+        /** Constructor, initialize with a config key and a default value. */
+        public FloatValue(String key, float defaultValue) {
+            mKey = key;
+            mDefaultValue = defaultValue;
+            mValue = mDefaultValue;
+        }
+
+        /** Read a value from {@link KeyValueListParser} */
+        public void parse(KeyValueListParser parser) {
+            mValue = parser.getFloat(mKey, mDefaultValue);
+        }
+
+        /** Return the config key. */
+        public String getKey() {
+            return mKey;
+        }
+
+        /** Return the default value. */
+        public float getDefaultValue() {
+            return mDefaultValue;
+        }
+
+        /** Return the actual config value. */
+        public float getValue() {
+            return mValue;
+        }
+
+        /** Overwrites with a value. */
+        public void setValue(float value) {
+            mValue = value;
+        }
+
+        /** Used for dumpsys */
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix);
+            pw.print(mKey);
+            pw.print("=");
+            pw.print(mValue);
+            pw.println();
+        }
+
+        /** Used for proto dumpsys */
+        public void dumpProto(ProtoOutputStream proto, long tag) {
+            proto.write(tag, mValue);
+        }
+    }
+}
diff --git a/android/util/KeyValueSettingObserver.java b/android/util/KeyValueSettingObserver.java
new file mode 100644
index 0000000..9fca8b2
--- /dev/null
+++ b/android/util/KeyValueSettingObserver.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+
+/**
+ * Abstract class for observing changes to a specified setting stored as a comma-separated key value
+ * list of parameters. Registers and unregisters a {@link ContentObserver} and handles updates when
+ * the setting changes.
+ *
+ * <p>Subclasses should pass in the relevant setting's {@link Uri} in the constructor and implement
+ * {@link #update(KeyValueListParser)} to receive updates when the value changes.
+ * Calls to {@link #update(KeyValueListParser)} only trigger after calling {@link
+ * #start()}.
+ *
+ * <p>To get the most up-to-date parameter values, first call {@link #start()} before accessing the
+ * values to start observing changes, and then call {@link #stop()} once finished.
+ *
+ * @hide
+ */
+public abstract class KeyValueSettingObserver {
+    private static final String TAG = "KeyValueSettingObserver";
+
+    private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+    private final ContentObserver mObserver;
+    private final ContentResolver mResolver;
+    private final Uri mSettingUri;
+
+    public KeyValueSettingObserver(Handler handler, ContentResolver resolver,
+            Uri uri) {
+        mObserver = new SettingObserver(handler);
+        mResolver = resolver;
+        mSettingUri = uri;
+    }
+
+    /** Starts observing changes for the setting. Pair with {@link #stop()}. */
+    public void start() {
+        mResolver.registerContentObserver(mSettingUri, false, mObserver);
+        setParserValue();
+        update(mParser);
+    }
+
+    /** Stops observing changes for the setting. */
+    public void stop() {
+        mResolver.unregisterContentObserver(mObserver);
+    }
+
+    /**
+     * Returns the {@link String} representation of the setting. Subclasses should implement this
+     * for their setting.
+     */
+    public abstract String getSettingValue(ContentResolver resolver);
+
+    /** Updates the parser with the current setting value. */
+    private void setParserValue() {
+        String setting = getSettingValue(mResolver);
+        try {
+            mParser.setString(setting);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Malformed setting: " + setting);
+        }
+    }
+
+    /** Subclasses should implement this to update references to their parameters. */
+    public abstract void update(KeyValueListParser parser);
+
+    private class SettingObserver extends ContentObserver {
+        private SettingObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            setParserValue();
+            update(mParser);
+        }
+    }
+}
diff --git a/android/util/LauncherIcons.java b/android/util/LauncherIcons.java
new file mode 100644
index 0000000..e652e17
--- /dev/null
+++ b/android/util/LauncherIcons.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.app.ActivityThread;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.graphics.drawable.LayerDrawable;
+
+/**
+ * Utility class to handle icon treatments (e.g., shadow generation) for the Launcher icons.
+ * @hide
+ */
+public final class LauncherIcons {
+
+    // Percent of actual icon size
+    private static final float ICON_SIZE_BLUR_FACTOR = 0.5f/48;
+    // Percent of actual icon size
+    private static final float ICON_SIZE_KEY_SHADOW_DELTA_FACTOR = 1f/48;
+
+    private static final int KEY_SHADOW_ALPHA = 61;
+    private static final int AMBIENT_SHADOW_ALPHA = 30;
+
+    private final SparseArray<Bitmap> mShadowCache = new SparseArray<>();
+    private final int mIconSize;
+    private final Resources mRes;
+
+    public LauncherIcons(Context context) {
+        mRes = context.getResources();
+        mIconSize = mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
+    }
+
+    public Drawable wrapIconDrawableWithShadow(Drawable drawable) {
+        if (!(drawable instanceof AdaptiveIconDrawable)) {
+            return drawable;
+        }
+        Bitmap shadow = getShadowBitmap((AdaptiveIconDrawable) drawable);
+        return new ShadowDrawable(shadow, drawable);
+    }
+
+    private Bitmap getShadowBitmap(AdaptiveIconDrawable d) {
+        int shadowSize = Math.max(mIconSize, d.getIntrinsicHeight());
+        synchronized (mShadowCache) {
+            Bitmap shadow = mShadowCache.get(shadowSize);
+            if (shadow != null) {
+                return shadow;
+            }
+        }
+
+        d.setBounds(0, 0, shadowSize, shadowSize);
+
+        float blur = ICON_SIZE_BLUR_FACTOR * shadowSize;
+        float keyShadowDistance = ICON_SIZE_KEY_SHADOW_DELTA_FACTOR * shadowSize;
+
+        int bitmapSize = (int) (shadowSize + 2 * blur + keyShadowDistance);
+        Bitmap shadow = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
+
+        Canvas canvas = new Canvas(shadow);
+        canvas.translate(blur + keyShadowDistance / 2, blur);
+
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setColor(Color.TRANSPARENT);
+
+        // Draw ambient shadow
+        paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
+        canvas.drawPath(d.getIconMask(), paint);
+
+        // Draw key shadow
+        canvas.translate(0, keyShadowDistance);
+        paint.setShadowLayer(blur, 0, 0, KEY_SHADOW_ALPHA << 24);
+        canvas.drawPath(d.getIconMask(), paint);
+
+        canvas.setBitmap(null);
+        synchronized (mShadowCache) {
+            mShadowCache.put(shadowSize, shadow);
+        }
+        return shadow;
+    }
+
+    public Drawable getBadgeDrawable(int foregroundRes, int backgroundColor) {
+        return getBadgedDrawable(null, foregroundRes, backgroundColor);
+    }
+
+    public Drawable getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor) {
+        Resources overlayableRes =
+                ActivityThread.currentActivityThread().getApplication().getResources();
+
+        // ic_corp_icon_badge_shadow is not work-profile-specific.
+        Drawable badgeShadow = overlayableRes.getDrawable(
+                com.android.internal.R.drawable.ic_corp_icon_badge_shadow);
+
+        // ic_corp_icon_badge_color is not work-profile-specific.
+        Drawable badgeColor = overlayableRes.getDrawable(
+                com.android.internal.R.drawable.ic_corp_icon_badge_color)
+                .getConstantState().newDrawable().mutate();
+
+        Drawable badgeForeground = overlayableRes.getDrawable(foregroundRes);
+        badgeForeground.setTint(backgroundColor);
+
+        Drawable[] drawables = base == null
+                ? new Drawable[] {badgeShadow, badgeColor, badgeForeground }
+                : new Drawable[] {base, badgeShadow, badgeColor, badgeForeground };
+        return new LayerDrawable(drawables);
+    }
+
+    /**
+     * A drawable which draws a shadow bitmap behind a drawable
+     */
+    private static class ShadowDrawable extends DrawableWrapper {
+
+        final MyConstantState mState;
+
+        public ShadowDrawable(Bitmap shadow, Drawable dr) {
+            super(dr);
+            mState = new MyConstantState(shadow, dr.getConstantState());
+        }
+
+        ShadowDrawable(MyConstantState state) {
+            super(state.mChildState.newDrawable());
+            mState = state;
+        }
+
+        @Override
+        public ConstantState getConstantState() {
+            return mState;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            Rect bounds = getBounds();
+            canvas.drawBitmap(mState.mShadow, null, bounds, mState.mPaint);
+            canvas.save();
+            // Ratio of child drawable size to shadow bitmap size
+            float factor = 1 / (1 + 2 * ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR);
+
+            canvas.translate(
+                    bounds.width() * factor *
+                            (ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR / 2),
+                    bounds.height() * factor * ICON_SIZE_BLUR_FACTOR);
+            canvas.scale(factor, factor);
+            super.draw(canvas);
+            canvas.restore();
+        }
+
+        private static class MyConstantState extends ConstantState {
+
+            final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+            final Bitmap mShadow;
+            final ConstantState mChildState;
+
+            MyConstantState(Bitmap shadow, ConstantState childState) {
+                mShadow = shadow;
+                mChildState = childState;
+            }
+
+            @Override
+            public Drawable newDrawable() {
+                return new ShadowDrawable(this);
+            }
+
+            @Override
+            public int getChangingConfigurations() {
+                return mChildState.getChangingConfigurations();
+            }
+        }
+    }
+}
diff --git a/android/util/LayoutDirection.java b/android/util/LayoutDirection.java
new file mode 100644
index 0000000..03077e4
--- /dev/null
+++ b/android/util/LayoutDirection.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+/**
+ * A class for defining layout directions. A layout direction can be left-to-right (LTR)
+ * or right-to-left (RTL). It can also be inherited (from a parent) or deduced from the default
+ * language script of a locale.
+ */
+public final class LayoutDirection {
+
+    // No instantiation
+    private LayoutDirection() {}
+
+    /**
+     * An undefined layout direction.
+     * @hide
+     */
+    public static final int UNDEFINED = -1;
+
+    /**
+     * Horizontal layout direction is from Left to Right.
+     */
+    public static final int LTR = 0;
+
+    /**
+     * Horizontal layout direction is from Right to Left.
+     */
+    public static final int RTL = 1;
+
+    /**
+     * Horizontal layout direction is inherited.
+     */
+    public static final int INHERIT = 2;
+
+    /**
+     * Horizontal layout direction is deduced from the default language script for the locale.
+     */
+    public static final int LOCALE = 3;
+}
diff --git a/android/util/LocalLog.java b/android/util/LocalLog.java
new file mode 100644
index 0000000..fda5e0d
--- /dev/null
+++ b/android/util/LocalLog.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.SystemClock;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+/**
+ * @hide
+ */
+public final class LocalLog {
+
+    private final Deque<String> mLog;
+    private final int mMaxLines;
+
+    /**
+     * {@code true} to use log timestamps expressed in local date/time, {@code false} to use log
+     * timestamped expressed with the elapsed realtime clock and UTC system clock. {@code false} is
+     * useful when logging behavior that modifies device time zone or system clock.
+     */
+    private final boolean mUseLocalTimestamps;
+
+    @UnsupportedAppUsage
+    public LocalLog(int maxLines) {
+        this(maxLines, true /* useLocalTimestamps */);
+    }
+
+    public LocalLog(int maxLines, boolean useLocalTimestamps) {
+        mMaxLines = Math.max(0, maxLines);
+        mLog = new ArrayDeque<>(mMaxLines);
+        mUseLocalTimestamps = useLocalTimestamps;
+    }
+
+    @UnsupportedAppUsage
+    public void log(String msg) {
+        if (mMaxLines <= 0) {
+            return;
+        }
+        final String logLine;
+        if (mUseLocalTimestamps) {
+            logLine = String.format("%s - %s", LocalDateTime.now(), msg);
+        } else {
+            logLine = String.format(
+                    "%s / %s - %s", SystemClock.elapsedRealtime(), Instant.now(), msg);
+        }
+        append(logLine);
+    }
+
+    private synchronized void append(String logLine) {
+        while (mLog.size() >= mMaxLines) {
+            mLog.remove();
+        }
+        mLog.add(logLine);
+    }
+
+    @UnsupportedAppUsage
+    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        dump(pw);
+    }
+
+    public synchronized void dump(PrintWriter pw) {
+        dump("", pw);
+    }
+
+    /**
+     * Dumps the content of local log to print writer with each log entry predeced with indent
+     *
+     * @param indent indent that precedes each log entry
+     * @param pw printer writer to write into
+     */
+    public synchronized void dump(String indent, PrintWriter pw) {
+        Iterator<String> itr = mLog.iterator();
+        while (itr.hasNext()) {
+            pw.printf("%s%s\n", indent, itr.next());
+        }
+    }
+
+    public synchronized void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        reverseDump(pw);
+    }
+
+    public synchronized void reverseDump(PrintWriter pw) {
+        Iterator<String> itr = mLog.descendingIterator();
+        while (itr.hasNext()) {
+            pw.println(itr.next());
+        }
+    }
+
+    public static class ReadOnlyLocalLog {
+        private final LocalLog mLog;
+        ReadOnlyLocalLog(LocalLog log) {
+            mLog = log;
+        }
+        @UnsupportedAppUsage
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            mLog.dump(pw);
+        }
+        public void dump(PrintWriter pw) {
+            mLog.dump(pw);
+        }
+        public void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            mLog.reverseDump(pw);
+        }
+        public void reverseDump(PrintWriter pw) {
+            mLog.reverseDump(pw);
+        }
+    }
+
+    @UnsupportedAppUsage
+    public ReadOnlyLocalLog readOnlyLocalLog() {
+        return new ReadOnlyLocalLog(this);
+    }
+}
diff --git a/android/util/Log.java b/android/util/Log.java
new file mode 100644
index 0000000..b94e48b
--- /dev/null
+++ b/android/util/Log.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2006 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;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.UnknownHostException;
+
+/**
+ * Mock Log implementation for testing on non android host.
+ */
+public final class Log {
+
+    /**
+     * Priority constant for the println method; use Log.v.
+     */
+    public static final int VERBOSE = 2;
+
+    /**
+     * Priority constant for the println method; use Log.d.
+     */
+    public static final int DEBUG = 3;
+
+    /**
+     * Priority constant for the println method; use Log.i.
+     */
+    public static final int INFO = 4;
+
+    /**
+     * Priority constant for the println method; use Log.w.
+     */
+    public static final int WARN = 5;
+
+    /**
+     * Priority constant for the println method; use Log.e.
+     */
+    public static final int ERROR = 6;
+
+    /**
+     * Priority constant for the println method.
+     */
+    public static final int ASSERT = 7;
+
+    private Log() {
+    }
+
+    /**
+     * Send a {@link #VERBOSE} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int v(String tag, String msg) {
+        return println(LOG_ID_MAIN, VERBOSE, tag, msg);
+    }
+
+    /**
+     * Send a {@link #VERBOSE} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int v(String tag, String msg, Throwable tr) {
+        return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
+    }
+
+    /**
+     * Send a {@link #DEBUG} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int d(String tag, String msg) {
+        return println(LOG_ID_MAIN, DEBUG, tag, msg);
+    }
+
+    /**
+     * Send a {@link #DEBUG} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int d(String tag, String msg, Throwable tr) {
+        return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
+    }
+
+    /**
+     * Send an {@link #INFO} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int i(String tag, String msg) {
+        return println(LOG_ID_MAIN, INFO, tag, msg);
+    }
+
+    /**
+     * Send a {@link #INFO} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int i(String tag, String msg, Throwable tr) {
+        return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
+    }
+
+    /**
+     * Send a {@link #WARN} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int w(String tag, String msg) {
+        return println(LOG_ID_MAIN, WARN, tag, msg);
+    }
+
+    /**
+     * Send a {@link #WARN} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int w(String tag, String msg, Throwable tr) {
+        return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
+    }
+
+    /*
+     * Send a {@link #WARN} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     */
+    public static int w(String tag, Throwable tr) {
+        return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
+    }
+
+    /**
+     * Send an {@link #ERROR} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     */
+    public static int e(String tag, String msg) {
+        return println(LOG_ID_MAIN, ERROR, tag, msg);
+    }
+
+    /**
+     * Send a {@link #ERROR} log message and log the exception.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @param tr An exception to log
+     */
+    public static int e(String tag, String msg, Throwable tr) {
+        return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
+    }
+
+    /**
+     * Handy function to get a loggable stack trace from a Throwable
+     * @param tr An exception to log
+     */
+    public static String getStackTraceString(Throwable tr) {
+        if (tr == null) {
+            return "";
+        }
+
+        // This is to reduce the amount of log spew that apps do in the non-error
+        // condition of the network being unavailable.
+        Throwable t = tr;
+        while (t != null) {
+            if (t instanceof UnknownHostException) {
+                return "";
+            }
+            t = t.getCause();
+        }
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        tr.printStackTrace(pw);
+        pw.flush();
+        return sw.toString();
+    }
+
+    /**
+     * Low-level logging call.
+     * @param priority The priority/type of this log message
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param msg The message you would like logged.
+     * @return The number of bytes written.
+     */
+    public static int println(int priority, String tag, String msg) {
+        return println(LOG_ID_MAIN, priority, tag, msg);
+    }
+
+    /** @hide */ public static final int LOG_ID_MAIN = 0;
+    /** @hide */ public static final int LOG_ID_RADIO = 1;
+    /** @hide */ public static final int LOG_ID_EVENTS = 2;
+    /** @hide */ public static final int LOG_ID_SYSTEM = 3;
+    /** @hide */ public static final int LOG_ID_CRASH = 4;
+
+    /** @hide */ @SuppressWarnings("unused")
+    public static int println(int bufID,
+            int priority, String tag, String msg) {
+        return 0;
+    }
+}
diff --git a/android/util/LogPrinter.java b/android/util/LogPrinter.java
new file mode 100644
index 0000000..68f64d0
--- /dev/null
+++ b/android/util/LogPrinter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * Implementation of a {@link android.util.Printer} that sends its output
+ * to the system log.
+ */
+public class LogPrinter implements Printer {
+    private final int mPriority;
+    private final String mTag;
+    private final int mBuffer;
+    
+    /**
+     * Create a new Printer that sends to the log with the given priority
+     * and tag.
+     *
+     * @param priority The desired log priority:
+     * {@link android.util.Log#VERBOSE Log.VERBOSE},
+     * {@link android.util.Log#DEBUG Log.DEBUG},
+     * {@link android.util.Log#INFO Log.INFO},
+     * {@link android.util.Log#WARN Log.WARN}, or
+     * {@link android.util.Log#ERROR Log.ERROR}.
+     * @param tag A string tag to associate with each printed log statement.
+     */
+    public LogPrinter(int priority, String tag) {
+        mPriority = priority;
+        mTag = tag;
+        mBuffer = Log.LOG_ID_MAIN;
+    }
+
+    /**
+     * @hide
+     * Same as above, but buffer is one of the LOG_ID_ constants from android.util.Log.
+     */
+    public LogPrinter(int priority, String tag, int buffer) {
+        mPriority = priority;
+        mTag = tag;
+        mBuffer = buffer;
+    }
+    
+    public void println(String x) {
+        Log.println_native(mBuffer, mPriority, mTag, x);
+    }
+}
diff --git a/android/util/LogWriter.java b/android/util/LogWriter.java
new file mode 100644
index 0000000..a674ae1
--- /dev/null
+++ b/android/util/LogWriter.java
@@ -0,0 +1,85 @@
+/*
+ * 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 android.util;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import java.io.Writer;
+
+/** @hide */
+public class LogWriter extends Writer {
+    private final int mPriority;
+    private final String mTag;
+    private final int mBuffer;
+    private StringBuilder mBuilder = new StringBuilder(128);
+
+    /**
+     * Create a new Writer that sends to the log with the given priority
+     * and tag.
+     *
+     * @param priority The desired log priority:
+     * {@link android.util.Log#VERBOSE Log.VERBOSE},
+     * {@link android.util.Log#DEBUG Log.DEBUG},
+     * {@link android.util.Log#INFO Log.INFO},
+     * {@link android.util.Log#WARN Log.WARN}, or
+     * {@link android.util.Log#ERROR Log.ERROR}.
+     * @param tag A string tag to associate with each printed log statement.
+     */
+    @UnsupportedAppUsage
+    public LogWriter(int priority, String tag) {
+        mPriority = priority;
+        mTag = tag;
+        mBuffer = Log.LOG_ID_MAIN;
+    }
+
+    /**
+     * @hide
+     * Same as above, but buffer is one of the LOG_ID_ constants from android.util.Log.
+     */
+    public LogWriter(int priority, String tag, int buffer) {
+        mPriority = priority;
+        mTag = tag;
+        mBuffer = buffer;
+    }
+
+    @Override public void close() {
+        flushBuilder();
+    }
+
+    @Override public void flush() {
+        flushBuilder();
+    }
+
+    @Override public void write(char[] buf, int offset, int count) {
+        for(int i = 0; i < count; i++) {
+            char c = buf[offset + i];
+            if ( c == '\n') {
+                flushBuilder();
+            }
+            else {
+                mBuilder.append(c);
+            }
+        }
+    }
+
+    private void flushBuilder() {
+        if (mBuilder.length() > 0) {
+            Log.println_native(mBuffer, mPriority, mTag, mBuilder.toString());
+            mBuilder.delete(0, mBuilder.length());
+        }
+    }
+}
diff --git a/android/util/Log_Delegate.java b/android/util/Log_Delegate.java
new file mode 100644
index 0000000..7f432ab
--- /dev/null
+++ b/android/util/Log_Delegate.java
@@ -0,0 +1,51 @@
+/*
+ * 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 android.util;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+class Log_Delegate {
+    // to replicate prefix visible when using 'adb logcat'
+    private static char priorityChar(int priority) {
+        switch (priority) {
+            case Log.VERBOSE:
+                return 'V';
+            case Log.DEBUG:
+                return 'D';
+            case Log.INFO:
+                return 'I';
+            case Log.WARN:
+                return 'W';
+            case Log.ERROR:
+                return 'E';
+            case Log.ASSERT:
+                return 'A';
+            default:
+                return '?';
+        }
+    }
+
+    @LayoutlibDelegate
+    static int println_native(int bufID, int priority, String tag, String msgs) {
+        String prefix = priorityChar(priority) + "/" + tag + ": ";
+        for (String msg: msgs.split("\n")) {
+            System.out.println(prefix + msg);
+        }
+        return 0;
+    }
+
+}
diff --git a/android/util/LongArray.java b/android/util/LongArray.java
new file mode 100644
index 0000000..93bcd6b
--- /dev/null
+++ b/android/util/LongArray.java
@@ -0,0 +1,236 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+
+/**
+ * Implements a growing array of long primitives.
+ *
+ * @hide
+ */
+public class LongArray implements Cloneable {
+    private static final int MIN_CAPACITY_INCREMENT = 12;
+
+    private long[] mValues;
+    private int mSize;
+
+    private  LongArray(long[] array, int size) {
+        mValues = array;
+        mSize = Preconditions.checkArgumentInRange(size, 0, array.length, "size");
+    }
+
+    /**
+     * Creates an empty LongArray with the default initial capacity.
+     */
+    @UnsupportedAppUsage
+    public LongArray() {
+        this(10);
+    }
+
+    /**
+     * Creates an empty LongArray with the specified initial capacity.
+     */
+    public LongArray(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mValues = EmptyArray.LONG;
+        } else {
+            mValues = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+        }
+        mSize = 0;
+    }
+
+    /**
+     * Creates an LongArray wrapping the given primitive long array.
+     */
+    public static LongArray wrap(long[] array) {
+        return new LongArray(array, array.length);
+    }
+
+    /**
+     * Creates an LongArray from the given primitive long array, copying it.
+     */
+    public static LongArray fromArray(long[] array, int size) {
+        return wrap(Arrays.copyOf(array, size));
+    }
+
+    /**
+     * Changes the size of this LongArray. If this LongArray is shrinked, the backing array capacity
+     * is unchanged. If the new size is larger than backing array capacity, a new backing array is
+     * created from the current content of this LongArray padded with 0s.
+     */
+    public void resize(int newSize) {
+        Preconditions.checkArgumentNonnegative(newSize);
+        if (newSize <= mValues.length) {
+            Arrays.fill(mValues, newSize, mValues.length, 0);
+        } else {
+            ensureCapacity(newSize - mSize);
+        }
+        mSize = newSize;
+    }
+
+    /**
+     * Appends the specified value to the end of this array.
+     */
+    public void add(long value) {
+        add(mSize, value);
+    }
+
+    /**
+     * Inserts a value at the specified position in this array. If the specified index is equal to
+     * the length of the array, the value is added at the end.
+     *
+     * @throws IndexOutOfBoundsException when index &lt; 0 || index &gt; size()
+     */
+    @UnsupportedAppUsage
+    public void add(int index, long value) {
+        ensureCapacity(1);
+        int rightSegment = mSize - index;
+        mSize++;
+        ArrayUtils.checkBounds(mSize, index);
+
+        if (rightSegment != 0) {
+            // Move by 1 all values from the right of 'index'
+            System.arraycopy(mValues, index, mValues, index + 1, rightSegment);
+        }
+
+        mValues[index] = value;
+    }
+
+    /**
+     * Adds the values in the specified array to this array.
+     */
+    public void addAll(LongArray values) {
+        final int count = values.mSize;
+        ensureCapacity(count);
+
+        System.arraycopy(values.mValues, 0, mValues, mSize, count);
+        mSize += count;
+    }
+
+    /**
+     * Ensures capacity to append at least <code>count</code> values.
+     */
+    private void ensureCapacity(int count) {
+        final int currentSize = mSize;
+        final int minCapacity = currentSize + count;
+        if (minCapacity >= mValues.length) {
+            final int targetCap = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2) ?
+                    MIN_CAPACITY_INCREMENT : currentSize >> 1);
+            final int newCapacity = targetCap > minCapacity ? targetCap : minCapacity;
+            final long[] newValues = ArrayUtils.newUnpaddedLongArray(newCapacity);
+            System.arraycopy(mValues, 0, newValues, 0, currentSize);
+            mValues = newValues;
+        }
+    }
+
+    /**
+     * Removes all values from this array.
+     */
+    public void clear() {
+        mSize = 0;
+    }
+
+    @Override
+    public LongArray clone() {
+        LongArray clone = null;
+        try {
+            clone = (LongArray) super.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Returns the value at the specified position in this array.
+     */
+    @UnsupportedAppUsage
+    public long get(int index) {
+        ArrayUtils.checkBounds(mSize, index);
+        return mValues[index];
+    }
+
+    /**
+     * Sets the value at the specified position in this array.
+     */
+    public void set(int index, long value) {
+        ArrayUtils.checkBounds(mSize, index);
+        mValues[index] = value;
+    }
+
+    /**
+     * Returns the index of the first occurrence of the specified value in this
+     * array, or -1 if this array does not contain the value.
+     */
+    public int indexOf(long value) {
+        final int n = mSize;
+        for (int i = 0; i < n; i++) {
+            if (mValues[i] == value) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Removes the value at the specified index from this array.
+     */
+    public void remove(int index) {
+        ArrayUtils.checkBounds(mSize, index);
+        System.arraycopy(mValues, index + 1, mValues, index, mSize - index - 1);
+        mSize--;
+    }
+
+    /**
+     * Returns the number of values in this array.
+     */
+    @UnsupportedAppUsage
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Returns a new array with the contents of this LongArray.
+     */
+    public long[] toArray() {
+        return Arrays.copyOf(mValues, mSize);
+    }
+
+    /**
+     * Test if each element of {@code a} equals corresponding element from {@code b}
+     */
+    public static boolean elementsEqual(@Nullable LongArray a, @Nullable LongArray b) {
+        if (a == null || b == null) return a == b;
+        if (a.mSize != b.mSize) return false;
+        for (int i = 0; i < a.mSize; i++) {
+            if (a.get(i) != b.get(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/android/util/LongArrayQueue.java b/android/util/LongArrayQueue.java
new file mode 100644
index 0000000..5c701db
--- /dev/null
+++ b/android/util/LongArrayQueue.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2019 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;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A lightweight implementation for a queue with long values.
+ * Additionally supports getting an element with a specified position from the head of the queue.
+ * The queue grows in size if needed to accommodate new elements.
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+    private long[] mValues;
+    private int mSize;
+    private int mHead;
+    private int mTail;
+
+    /**
+     * Initializes a queue with the given starting capacity.
+     *
+     * @param initialCapacity the capacity.
+     */
+    public LongArrayQueue(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mValues = EmptyArray.LONG;
+        } else {
+            mValues = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+        }
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Initializes a queue with default starting capacity.
+     */
+    public LongArrayQueue() {
+        this(16);
+    }
+
+    private void grow() {
+        if (mSize < mValues.length) {
+            throw new IllegalStateException("Queue not full yet!");
+        }
+        final int newSize = GrowingArrayUtils.growSize(mSize);
+        final long[] newArray = ArrayUtils.newUnpaddedLongArray(newSize);
+        final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+        System.arraycopy(mValues, mHead, newArray, 0, r);
+        System.arraycopy(mValues, 0, newArray, r, mHead);
+        mValues = newArray;
+        mHead = 0;
+        mTail = mSize;
+    }
+
+    /**
+     * Returns the number of elements in the queue.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Removes all elements from this queue.
+     */
+    public void clear() {
+        mSize = 0;
+        mHead = mTail = 0;
+    }
+
+    /**
+     * Adds a value to the tail of the queue.
+     *
+     * @param value the value to be added.
+     */
+    public void addLast(long value) {
+        if (mSize == mValues.length) {
+            grow();
+        }
+        mValues[mTail] = value;
+        mTail = (mTail + 1) % mValues.length;
+        mSize++;
+    }
+
+    /**
+     * Removes an element from the head of the queue.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long removeFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final long ret = mValues[mHead];
+        mHead = (mHead + 1) % mValues.length;
+        mSize--;
+        return ret;
+    }
+
+    /**
+     * Returns the element at the given position from the head of the queue, where 0 represents the
+     * head of the queue.
+     *
+     * @param position the position from the head of the queue.
+     * @return the element found at the given position.
+     * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+     *                                   {@code position} >= {@link #size()}
+     */
+    public long get(int position) {
+        if (position < 0 || position >= mSize) {
+            throw new IndexOutOfBoundsException("Index " + position
+                    + " not valid for a queue of size " + mSize);
+        }
+        final int index = (mHead + position) % mValues.length;
+        return mValues[index];
+    }
+
+    /**
+     * Returns the element at the head of the queue, without removing it.
+     *
+     * @return the element at the head of the queue.
+     * @throws NoSuchElementException if the queue is empty
+     */
+    public long peekFirst() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        return mValues[mHead];
+    }
+
+    /**
+     * Returns the element at the tail of the queue.
+     *
+     * @return the element at the tail of the queue.
+     * @throws NoSuchElementException if the queue is empty.
+     */
+    public long peekLast() {
+        if (mSize == 0) {
+            throw new NoSuchElementException("Queue is empty!");
+        }
+        final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+        return mValues[index];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        if (mSize <= 0) {
+            return "{}";
+        }
+
+        final StringBuilder buffer = new StringBuilder(mSize * 64);
+        buffer.append('{');
+        buffer.append(get(0));
+        for (int i = 1; i < mSize; i++) {
+            buffer.append(", ");
+            buffer.append(get(i));
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+}
diff --git a/android/util/LongSparseArray.java b/android/util/LongSparseArray.java
new file mode 100644
index 0000000..55542d8
--- /dev/null
+++ b/android/util/LongSparseArray.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2009 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.LongObjPredicate;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * SparseArray mapping longs to Objects.  Unlike a normal array of Objects,
+ * there can be gaps in the indices.  It is intended to be more memory efficient
+ * than using a HashMap to map Longs to Objects, both because it avoids
+ * auto-boxing keys and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys.  The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items.  It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array.  For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>To help with performance, the container includes an optimization when removing
+ * keys: instead of compacting its array immediately, it leaves the removed entry marked
+ * as deleted.  The entry can then be re-used for the same key, or compacted later in
+ * a single garbage collection step of all removed entries.  This garbage collection will
+ * need to be performed at any time the array needs to be grown or the the map size or
+ * entry values are retrieved.</p>
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)</code>.</p>
+ */
+public class LongSparseArray<E> implements Cloneable {
+    private static final Object DELETED = new Object();
+    private boolean mGarbage = false;
+
+    private long[] mKeys;
+    private Object[] mValues;
+    private int mSize;
+
+    /**
+     * Creates a new LongSparseArray containing no mappings.
+     */
+    public LongSparseArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new LongSparseArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.  If you supply an initial capacity of 0, the
+     * sparse array will be initialized with a light-weight representation
+     * not requiring any additional array allocations.
+     */
+    public LongSparseArray(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mKeys = EmptyArray.LONG;
+            mValues = EmptyArray.OBJECT;
+        } else {
+            mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+            mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
+        }
+        mSize = 0;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public LongSparseArray<E> clone() {
+        LongSparseArray<E> clone = null;
+        try {
+            clone = (LongSparseArray<E>) super.clone();
+            clone.mKeys = mKeys.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or <code>null</code>
+     * if no such mapping has been made.
+     */
+    public E get(long key) {
+        return get(key, null);
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or the specified Object
+     * if no such mapping has been made.
+     */
+    @SuppressWarnings("unchecked")
+    public E get(long key, E valueIfKeyNotFound) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i < 0 || mValues[i] == DELETED) {
+            return valueIfKeyNotFound;
+        } else {
+            return (E) mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(long key) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            if (mValues[i] != DELETED) {
+                mValues[i] = DELETED;
+                mGarbage = true;
+            }
+        }
+    }
+
+    /**
+     * Alias for {@link #delete(long)}.
+     */
+    public void remove(long key) {
+        delete(key);
+    }
+
+    /** @hide */
+    @SuppressWarnings("unchecked")
+    public void removeIf(@NonNull LongObjPredicate<? super E> filter) {
+        Objects.requireNonNull(filter);
+        for (int i = 0; i < mSize; ++i) {
+            if (mValues[i] != DELETED && filter.test(mKeys[i], (E) mValues[i])) {
+                mValues[i] = DELETED;
+                mGarbage = true;
+            }
+        }
+    }
+
+    /**
+     * Removes the mapping at the specified index.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public void removeAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        if (mValues[index] != DELETED) {
+            mValues[index] = DELETED;
+            mGarbage = true;
+        }
+    }
+
+    private void gc() {
+        // Log.e("SparseArray", "gc start with " + mSize);
+
+        int n = mSize;
+        int o = 0;
+        long[] keys = mKeys;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            Object val = values[i];
+
+            if (val != DELETED) {
+                if (i != o) {
+                    keys[o] = keys[i];
+                    values[o] = val;
+                    values[i] = null;
+                }
+
+                o++;
+            }
+        }
+
+        mGarbage = false;
+        mSize = o;
+
+        // Log.e("SparseArray", "gc end with " + mSize);
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(long key, E value) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            if (i < mSize && mValues[i] == DELETED) {
+                mKeys[i] = key;
+                mValues[i] = value;
+                return;
+            }
+
+            if (mGarbage && mSize >= mKeys.length) {
+                gc();
+
+                // Search again because indices may have changed.
+                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
+            }
+
+            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this LongSparseArray
+     * currently stores.
+     */
+    public int size() {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     *
+     * <p>The keys corresponding to indices in ascending order are guaranteed to
+     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+     * smallest key and <code>keyAt(size()-1)</code> will return the largest
+     * key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public long keyAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        if (mGarbage) {
+            gc();
+        }
+
+        return mKeys[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     *
+     * <p>The values corresponding to indices in ascending order are guaranteed
+     * to be associated with keys in ascending order, e.g.,
+     * <code>valueAt(0)</code> will return the value associated with the
+     * smallest key and <code>valueAt(size()-1)</code> will return the value
+     * associated with the largest key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    @SuppressWarnings("unchecked")
+    public E valueAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        if (mGarbage) {
+            gc();
+        }
+
+        return (E) mValues[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public void setValueAt(int index, E value) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        if (mGarbage) {
+            gc();
+        }
+
+        mValues[index] = value;
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(long key) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        for (int i = 0; i < mSize; i++) {
+            if (mValues[i] == value) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * <p>Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
+     * @hide
+     */
+    public int indexOfValueByValue(E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        for (int i = 0; i < mSize; i++) {
+            if (value == null) {
+                if (mValues[i] == null) {
+                    return i;
+                }
+            } else {
+                if (value.equals(mValues[i])) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this LongSparseArray.
+     */
+    public void clear() {
+        int n = mSize;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            values[i] = null;
+        }
+
+        mSize = 0;
+        mGarbage = false;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(long key, E value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        if (mGarbage && mSize >= mKeys.length) {
+            gc();
+        }
+
+        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+        mValues = GrowingArrayUtils.append(mValues, mSize, value);
+        mSize++;
+    }
+
+    /**
+     * {@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 "{}";
+        }
+
+        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();
+    }
+
+    /**
+     * @hide
+     */
+    public static class StringParcelling implements
+            com.android.internal.util.Parcelling<LongSparseArray<String>> {
+        @Override
+        public void parcel(LongSparseArray<String> array, Parcel dest, int parcelFlags) {
+            if (array == null) {
+                dest.writeInt(-1);
+                return;
+            }
+
+            int size = array.mSize;
+
+            dest.writeInt(size);
+            dest.writeLongArray(array.mKeys);
+
+            dest.writeStringArray(Arrays.copyOfRange(array.mValues, 0, size, String[].class));
+        }
+
+        @Override
+        public LongSparseArray<String> unparcel(Parcel source) {
+            int size = source.readInt();
+            if (size == -1) {
+                return null;
+            }
+
+            LongSparseArray<String> array = new LongSparseArray<>(0);
+            array.mSize = size;
+            array.mKeys = source.createLongArray();
+            array.mValues = source.createStringArray();
+
+            // Make sure array is sane
+            Preconditions.checkArgument(array.mKeys.length >= size);
+            Preconditions.checkArgument(array.mValues.length >= size);
+
+            if (size > 0) {
+                long last = array.mKeys[0];
+                for (int i = 1; i < size; i++) {
+                    Preconditions.checkArgument(last < array.mKeys[i]);
+                }
+            }
+
+            return array;
+        }
+    }
+}
diff --git a/android/util/LongSparseLongArray.java b/android/util/LongSparseLongArray.java
new file mode 100644
index 0000000..c05dd9d
--- /dev/null
+++ b/android/util/LongSparseLongArray.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import libcore.util.EmptyArray;
+
+/**
+ * Map of {@code long} to {@code long}. Unlike a normal array of longs, there
+ * can be gaps in the indices. It is intended to be more memory efficient than using a
+ * {@code HashMap}, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys.  The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items.  It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array.  For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)</code>.</p>
+ *
+ * @hide
+ */
+public class LongSparseLongArray implements Cloneable {
+    @UnsupportedAppUsage(maxTargetSdk = 28) // The type isn't even public.
+    private long[] mKeys;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // The type isn't even public.
+    private long[] mValues;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // The type isn't even public.
+    private int mSize;
+
+    /**
+     * Creates a new SparseLongArray containing no mappings.
+     */
+    public LongSparseLongArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new SparseLongArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.  If you supply an initial capacity of 0, the
+     * sparse array will be initialized with a light-weight representation
+     * not requiring any additional array allocations.
+     */
+    public LongSparseLongArray(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mKeys = EmptyArray.LONG;
+            mValues = EmptyArray.LONG;
+        } else {
+            mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+            mValues = new long[mKeys.length];
+        }
+        mSize = 0;
+    }
+
+    @Override
+    public LongSparseLongArray clone() {
+        LongSparseLongArray clone = null;
+        try {
+            clone = (LongSparseLongArray) super.clone();
+            clone.mKeys = mKeys.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Gets the long mapped from the specified key, or <code>0</code>
+     * if no such mapping has been made.
+     */
+    public long get(long key) {
+        return get(key, 0);
+    }
+
+    /**
+     * Gets the long mapped from the specified key, or the specified value
+     * if no such mapping has been made.
+     */
+    public long get(long key, long valueIfKeyNotFound) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i < 0) {
+            return valueIfKeyNotFound;
+        } else {
+            return mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(long key) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            removeAt(i);
+        }
+    }
+
+    /**
+     * Removes the mapping at the given index.
+     */
+    public void removeAt(int index) {
+        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+        mSize--;
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(long key, long value) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseIntArray
+     * currently stores.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseLongArray stores.
+     *
+     * <p>The keys corresponding to indices in ascending order are guaranteed to
+     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+     * smallest key and <code>keyAt(size()-1)</code> will return the largest
+     * key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public long keyAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return mKeys[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseLongArray stores.
+     *
+     * <p>The values corresponding to indices in ascending order are guaranteed
+     * to be associated with keys in ascending order, e.g.,
+     * <code>valueAt(0)</code> will return the value associated with the
+     * smallest key and <code>valueAt(size()-1)</code> will return the value
+     * associated with the largest key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public long valueAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return mValues[index];
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(long key) {
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(long value) {
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseIntArray.
+     */
+    public void clear() {
+        mSize = 0;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(long key, long value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+        mValues = GrowingArrayUtils.append(mValues, mSize, value);
+        mSize++;
+    }
+
+    /**
+     * {@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();
+    }
+
+    /**
+     * @hide
+     */
+    public static class Parcelling implements
+            com.android.internal.util.Parcelling<LongSparseLongArray> {
+        @Override
+        public void parcel(LongSparseLongArray array, Parcel dest, int parcelFlags) {
+            if (array == null) {
+                dest.writeInt(-1);
+                return;
+            }
+
+            dest.writeInt(array.mSize);
+            dest.writeLongArray(array.mKeys);
+            dest.writeLongArray(array.mValues);
+        }
+
+        @Override
+        public LongSparseLongArray unparcel(Parcel source) {
+            int size = source.readInt();
+            if (size == -1) {
+                return null;
+            }
+
+            LongSparseLongArray array = new LongSparseLongArray(0);
+
+            array.mSize = size;
+            array.mKeys = source.createLongArray();
+            array.mValues = source.createLongArray();
+
+            // Make sure array is sane
+            Preconditions.checkArgument(array.mKeys.length >= size);
+            Preconditions.checkArgument(array.mValues.length >= size);
+
+            if (size > 0) {
+                long last = array.mKeys[0];
+                for (int i = 1; i < size; i++) {
+                    Preconditions.checkArgument(last < array.mKeys[i]);
+                }
+            }
+
+            return array;
+        }
+    }
+}
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
new file mode 100644
index 0000000..3f7fdd8
--- /dev/null
+++ b/android/util/LruCache.java
@@ -0,0 +1,383 @@
+/*
+ * 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 android.util;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A cache that holds strong references to a limited number of values. Each time
+ * a value is accessed, it is moved to the head of a queue. When a value is
+ * added to a full cache, the value at the end of that queue is evicted and may
+ * become eligible for garbage collection.
+ *
+ * <p>If your cached values hold resources that need to be explicitly released,
+ * override {@link #entryRemoved}.
+ *
+ * <p>If a cache miss should be computed on demand for the corresponding keys,
+ * override {@link #create}. This simplifies the calling code, allowing it to
+ * assume a value will always be returned, even when there's a cache miss.
+ *
+ * <p>By default, the cache size is measured in the number of entries. Override
+ * {@link #sizeOf} to size the cache in different units. For example, this cache
+ * is limited to 4MiB of bitmaps:
+ * <pre>   {@code
+ *   int cacheSize = 4 * 1024 * 1024; // 4MiB
+ *   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
+ *       protected int sizeOf(String key, Bitmap value) {
+ *           return value.getByteCount();
+ *       }
+ *   }}</pre>
+ *
+ * <p>This class is thread-safe. Perform multiple cache operations atomically by
+ * synchronizing on the cache: <pre>   {@code
+ *   synchronized (cache) {
+ *     if (cache.get(key) == null) {
+ *         cache.put(key, value);
+ *     }
+ *   }}</pre>
+ *
+ * <p>This class does not allow null to be used as a key or value. A return
+ * value of null from {@link #get}, {@link #put} or {@link #remove} is
+ * unambiguous: the key was not in the cache.
+ *
+ * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
+ * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
+ * Support Package</a> for earlier releases.
+ */
+public class LruCache<K, V> {
+    @UnsupportedAppUsage
+    private final LinkedHashMap<K, V> map;
+
+    /** Size of this cache in units. Not necessarily the number of elements. */
+    private int size;
+    private int maxSize;
+
+    private int putCount;
+    private int createCount;
+    private int evictionCount;
+    private int hitCount;
+    private int missCount;
+
+    /**
+     * @param maxSize for caches that do not override {@link #sizeOf}, this is
+     *     the maximum number of entries in the cache. For all other caches,
+     *     this is the maximum sum of the sizes of the entries in this cache.
+     */
+    public LruCache(int maxSize) {
+        if (maxSize <= 0) {
+            throw new IllegalArgumentException("maxSize <= 0");
+        }
+        this.maxSize = maxSize;
+        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
+    }
+
+    /**
+     * Sets the size of the cache.
+     *
+     * @param maxSize The new maximum size.
+     */
+    public void resize(int maxSize) {
+        if (maxSize <= 0) {
+            throw new IllegalArgumentException("maxSize <= 0");
+        }
+
+        synchronized (this) {
+            this.maxSize = maxSize;
+        }
+        trimToSize(maxSize);
+    }
+
+    /**
+     * Returns the value for {@code key} if it exists in the cache or can be
+     * created by {@code #create}. If a value was returned, it is moved to the
+     * head of the queue. This returns null if a value is not cached and cannot
+     * be created.
+     */
+    public final V get(K key) {
+        if (key == null) {
+            throw new NullPointerException("key == null");
+        }
+
+        V mapValue;
+        synchronized (this) {
+            mapValue = map.get(key);
+            if (mapValue != null) {
+                hitCount++;
+                return mapValue;
+            }
+            missCount++;
+        }
+
+        /*
+         * Attempt to create a value. This may take a long time, and the map
+         * may be different when create() returns. If a conflicting value was
+         * added to the map while create() was working, we leave that value in
+         * the map and release the created value.
+         */
+
+        V createdValue = create(key);
+        if (createdValue == null) {
+            return null;
+        }
+
+        synchronized (this) {
+            createCount++;
+            mapValue = map.put(key, createdValue);
+
+            if (mapValue != null) {
+                // There was a conflict so undo that last put
+                map.put(key, mapValue);
+            } else {
+                size += safeSizeOf(key, createdValue);
+            }
+        }
+
+        if (mapValue != null) {
+            entryRemoved(false, key, createdValue, mapValue);
+            return mapValue;
+        } else {
+            trimToSize(maxSize);
+            return createdValue;
+        }
+    }
+
+    /**
+     * Caches {@code value} for {@code key}. The value is moved to the head of
+     * the queue.
+     *
+     * @return the previous value mapped by {@code key}.
+     */
+    public final V put(K key, V value) {
+        if (key == null || value == null) {
+            throw new NullPointerException("key == null || value == null");
+        }
+
+        V previous;
+        synchronized (this) {
+            putCount++;
+            size += safeSizeOf(key, value);
+            previous = map.put(key, value);
+            if (previous != null) {
+                size -= safeSizeOf(key, previous);
+            }
+        }
+
+        if (previous != null) {
+            entryRemoved(false, key, previous, value);
+        }
+
+        trimToSize(maxSize);
+        return previous;
+    }
+
+    /**
+     * Remove the eldest entries until the total of remaining entries is at or
+     * below the requested size.
+     *
+     * @param maxSize the maximum size of the cache before returning. May be -1
+     *            to evict even 0-sized elements.
+     */
+    public void trimToSize(int maxSize) {
+        while (true) {
+            K key;
+            V value;
+            synchronized (this) {
+                if (size < 0 || (map.isEmpty() && size != 0)) {
+                    throw new IllegalStateException(getClass().getName()
+                            + ".sizeOf() is reporting inconsistent results!");
+                }
+
+                if (size <= maxSize) {
+                    break;
+                }
+
+                Map.Entry<K, V> toEvict = map.eldest();
+                if (toEvict == null) {
+                    break;
+                }
+
+                key = toEvict.getKey();
+                value = toEvict.getValue();
+                map.remove(key);
+                size -= safeSizeOf(key, value);
+                evictionCount++;
+            }
+
+            entryRemoved(true, key, value, null);
+        }
+    }
+
+    /**
+     * Removes the entry for {@code key} if it exists.
+     *
+     * @return the previous value mapped by {@code key}.
+     */
+    public final V remove(K key) {
+        if (key == null) {
+            throw new NullPointerException("key == null");
+        }
+
+        V previous;
+        synchronized (this) {
+            previous = map.remove(key);
+            if (previous != null) {
+                size -= safeSizeOf(key, previous);
+            }
+        }
+
+        if (previous != null) {
+            entryRemoved(false, key, previous, null);
+        }
+
+        return previous;
+    }
+
+    /**
+     * Called for entries that have been evicted or removed. This method is
+     * invoked when a value is evicted to make space, removed by a call to
+     * {@link #remove}, or replaced by a call to {@link #put}. The default
+     * implementation does nothing.
+     *
+     * <p>The method is called without synchronization: other threads may
+     * access the cache while this method is executing.
+     *
+     * @param evicted true if the entry is being removed to make space, false
+     *     if the removal was caused by a {@link #put} or {@link #remove}.
+     * @param newValue the new value for {@code key}, if it exists. If non-null,
+     *     this removal was caused by a {@link #put} or a {@link #get}. Otherwise it was caused by
+     *     an eviction or a {@link #remove}.
+     */
+    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
+
+    /**
+     * Called after a cache miss to compute a value for the corresponding key.
+     * Returns the computed value or null if no value can be computed. The
+     * default implementation returns null.
+     *
+     * <p>The method is called without synchronization: other threads may
+     * access the cache while this method is executing.
+     *
+     * <p>If a value for {@code key} exists in the cache when this method
+     * returns, the created value will be released with {@link #entryRemoved}
+     * and discarded. This can occur when multiple threads request the same key
+     * at the same time (causing multiple values to be created), or when one
+     * thread calls {@link #put} while another is creating a value for the same
+     * key.
+     */
+    protected V create(K key) {
+        return null;
+    }
+
+    private int safeSizeOf(K key, V value) {
+        int result = sizeOf(key, value);
+        if (result < 0) {
+            throw new IllegalStateException("Negative size: " + key + "=" + value);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the size of the entry for {@code key} and {@code value} in
+     * user-defined units.  The default implementation returns 1 so that size
+     * is the number of entries and max size is the maximum number of entries.
+     *
+     * <p>An entry's size must not change while it is in the cache.
+     */
+    protected int sizeOf(K key, V value) {
+        return 1;
+    }
+
+    /**
+     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
+     */
+    public final void evictAll() {
+        trimToSize(-1); // -1 will evict 0-sized elements
+    }
+
+    /**
+     * For caches that do not override {@link #sizeOf}, this returns the number
+     * of entries in the cache. For all other caches, this returns the sum of
+     * the sizes of the entries in this cache.
+     */
+    public synchronized final int size() {
+        return size;
+    }
+
+    /**
+     * For caches that do not override {@link #sizeOf}, this returns the maximum
+     * number of entries in the cache. For all other caches, this returns the
+     * maximum sum of the sizes of the entries in this cache.
+     */
+    public synchronized final int maxSize() {
+        return maxSize;
+    }
+
+    /**
+     * Returns the number of times {@link #get} returned a value that was
+     * already present in the cache.
+     */
+    public synchronized final int hitCount() {
+        return hitCount;
+    }
+
+    /**
+     * Returns the number of times {@link #get} returned null or required a new
+     * value to be created.
+     */
+    public synchronized final int missCount() {
+        return missCount;
+    }
+
+    /**
+     * Returns the number of times {@link #create(Object)} returned a value.
+     */
+    public synchronized final int createCount() {
+        return createCount;
+    }
+
+    /**
+     * Returns the number of times {@link #put} was called.
+     */
+    public synchronized final int putCount() {
+        return putCount;
+    }
+
+    /**
+     * Returns the number of values that have been evicted.
+     */
+    public synchronized final int evictionCount() {
+        return evictionCount;
+    }
+
+    /**
+     * Returns a copy of the current contents of the cache, ordered from least
+     * recently accessed to most recently accessed.
+     */
+    public synchronized final Map<K, V> snapshot() {
+        return new LinkedHashMap<K, V>(map);
+    }
+
+    @Override public synchronized final String toString() {
+        int accesses = hitCount + missCount;
+        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
+        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
+                maxSize, hitCount, missCount, hitPercent);
+    }
+}
diff --git a/android/util/MalformedJsonException.java b/android/util/MalformedJsonException.java
new file mode 100644
index 0000000..63c19ff
--- /dev/null
+++ b/android/util/MalformedJsonException.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.util;
+
+import java.io.IOException;
+
+/**
+ * Thrown when a reader encounters malformed JSON. Some syntax errors can be
+ * ignored by calling {@link JsonReader#setLenient(boolean)}.
+ */
+public final class MalformedJsonException extends IOException {
+    private static final long serialVersionUID = 1L;
+
+    public MalformedJsonException(String message) {
+        super(message);
+    }
+}
diff --git a/android/util/MapCollections.java b/android/util/MapCollections.java
new file mode 100644
index 0000000..a521268
--- /dev/null
+++ b/android/util/MapCollections.java
@@ -0,0 +1,561 @@
+/*
+ * 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;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Helper for writing standard Java collection interfaces to a data
+ * structure like {@link ArrayMap}.
+ * @hide
+ */
+abstract class MapCollections<K, V> {
+    EntrySet mEntrySet;
+    KeySet mKeySet;
+    ValuesCollection mValues;
+
+    final class ArrayIterator<T> implements Iterator<T> {
+        final int mOffset;
+        int mSize;
+        int mIndex;
+        boolean mCanRemove = false;
+
+        ArrayIterator(int offset) {
+            mOffset = offset;
+            mSize = colGetSize();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return mIndex < mSize;
+        }
+
+        @Override
+        public T next() {
+            if (!hasNext()) throw new NoSuchElementException();
+            Object res = colGetEntry(mIndex, mOffset);
+            mIndex++;
+            mCanRemove = true;
+            return (T)res;
+        }
+
+        @Override
+        public void remove() {
+            if (!mCanRemove) {
+                throw new IllegalStateException();
+            }
+            mIndex--;
+            mSize--;
+            mCanRemove = false;
+            colRemoveAt(mIndex);
+        }
+    }
+
+    final class MapIterator implements Iterator<Map.Entry<K, V>>, Map.Entry<K, V> {
+        int mEnd;
+        int mIndex;
+        boolean mEntryValid = false;
+
+        MapIterator() {
+            mEnd = colGetSize() - 1;
+            mIndex = -1;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return mIndex < mEnd;
+        }
+
+        @Override
+        public Map.Entry<K, V> next() {
+            if (!hasNext()) throw new NoSuchElementException();
+            mIndex++;
+            mEntryValid = true;
+            return this;
+        }
+
+        @Override
+        public void remove() {
+            if (!mEntryValid) {
+                throw new IllegalStateException();
+            }
+            colRemoveAt(mIndex);
+            mIndex--;
+            mEnd--;
+            mEntryValid = false;
+        }
+
+        @Override
+        public K getKey() {
+            if (!mEntryValid) {
+                throw new IllegalStateException(
+                        "This container does not support retaining Map.Entry objects");
+            }
+            return (K)colGetEntry(mIndex, 0);
+        }
+
+        @Override
+        public V getValue() {
+            if (!mEntryValid) {
+                throw new IllegalStateException(
+                        "This container does not support retaining Map.Entry objects");
+            }
+            return (V)colGetEntry(mIndex, 1);
+        }
+
+        @Override
+        public V setValue(V object) {
+            if (!mEntryValid) {
+                throw new IllegalStateException(
+                        "This container does not support retaining Map.Entry objects");
+            }
+            return colSetValue(mIndex, object);
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (!mEntryValid) {
+                throw new IllegalStateException(
+                        "This container does not support retaining Map.Entry objects");
+            }
+            if (!(o instanceof Map.Entry)) {
+                return false;
+            }
+            Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
+            return Objects.equals(e.getKey(), colGetEntry(mIndex, 0))
+                    && Objects.equals(e.getValue(), colGetEntry(mIndex, 1));
+        }
+
+        @Override
+        public final int hashCode() {
+            if (!mEntryValid) {
+                throw new IllegalStateException(
+                        "This container does not support retaining Map.Entry objects");
+            }
+            final Object key = colGetEntry(mIndex, 0);
+            final Object value = colGetEntry(mIndex, 1);
+            return (key == null ? 0 : key.hashCode()) ^
+                    (value == null ? 0 : value.hashCode());
+        }
+
+        @Override
+        public final String toString() {
+            return getKey() + "=" + getValue();
+        }
+    }
+
+    final class EntrySet implements Set<Map.Entry<K, V>> {
+        @Override
+        public boolean add(Map.Entry<K, V> object) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean addAll(Collection<? extends Map.Entry<K, V>> collection) {
+            int oldSize = colGetSize();
+            for (Map.Entry<K, V> entry : collection) {
+                colPut(entry.getKey(), entry.getValue());
+            }
+            return oldSize != colGetSize();
+        }
+
+        @Override
+        public void clear() {
+            colClear();
+        }
+
+        @Override
+        public boolean contains(Object o) {
+            if (!(o instanceof Map.Entry))
+                return false;
+            Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
+            int index = colIndexOfKey(e.getKey());
+            if (index < 0) {
+                return false;
+            }
+            Object foundVal = colGetEntry(index, 1);
+            return Objects.equals(foundVal, e.getValue());
+        }
+
+        @Override
+        public boolean containsAll(Collection<?> collection) {
+            Iterator<?> it = collection.iterator();
+            while (it.hasNext()) {
+                if (!contains(it.next())) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return colGetSize() == 0;
+        }
+
+        @Override
+        public Iterator<Map.Entry<K, V>> iterator() {
+            return new MapIterator();
+        }
+
+        @Override
+        public boolean remove(Object object) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean removeAll(Collection<?> collection) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean retainAll(Collection<?> collection) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int size() {
+            return colGetSize();
+        }
+
+        @Override
+        public Object[] toArray() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> T[] toArray(T[] array) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            return equalsSetHelper(this, object);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            for (int i=colGetSize()-1; i>=0; i--) {
+                final Object key = colGetEntry(i, 0);
+                final Object value = colGetEntry(i, 1);
+                result += ( (key == null ? 0 : key.hashCode()) ^
+                        (value == null ? 0 : value.hashCode()) );
+            }
+            return result;
+        }
+    };
+
+    final class KeySet implements Set<K> {
+
+        @Override
+        public boolean add(K object) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean addAll(Collection<? extends K> collection) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void clear() {
+            colClear();
+        }
+
+        @Override
+        public boolean contains(Object object) {
+            return colIndexOfKey(object) >= 0;
+        }
+
+        @Override
+        public boolean containsAll(Collection<?> collection) {
+            return containsAllHelper(colGetMap(), collection);
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return colGetSize() == 0;
+        }
+
+        @Override
+        public Iterator<K> iterator() {
+            return new ArrayIterator<K>(0);
+        }
+
+        @Override
+        public boolean remove(Object object) {
+            int index = colIndexOfKey(object);
+            if (index >= 0) {
+                colRemoveAt(index);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean removeAll(Collection<?> collection) {
+            return removeAllHelper(colGetMap(), collection);
+        }
+
+        @Override
+        public boolean retainAll(Collection<?> collection) {
+            return retainAllHelper(colGetMap(), collection);
+        }
+
+        @Override
+        public int size() {
+            return colGetSize();
+        }
+
+        @Override
+        public Object[] toArray() {
+            return toArrayHelper(0);
+        }
+
+        @Override
+        public <T> T[] toArray(T[] array) {
+            return toArrayHelper(array, 0);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            return equalsSetHelper(this, object);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            for (int i=colGetSize()-1; i>=0; i--) {
+                Object obj = colGetEntry(i, 0);
+                result += obj == null ? 0 : obj.hashCode();
+            }
+            return result;
+        }
+    };
+
+    final class ValuesCollection implements Collection<V> {
+
+        @Override
+        public boolean add(V object) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean addAll(Collection<? extends V> collection) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void clear() {
+            colClear();
+        }
+
+        @Override
+        public boolean contains(Object object) {
+            return colIndexOfValue(object) >= 0;
+        }
+
+        @Override
+        public boolean containsAll(Collection<?> collection) {
+            Iterator<?> it = collection.iterator();
+            while (it.hasNext()) {
+                if (!contains(it.next())) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return colGetSize() == 0;
+        }
+
+        @Override
+        public Iterator<V> iterator() {
+            return new ArrayIterator<V>(1);
+        }
+
+        @Override
+        public boolean remove(Object object) {
+            int index = colIndexOfValue(object);
+            if (index >= 0) {
+                colRemoveAt(index);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean removeAll(Collection<?> collection) {
+            int N = colGetSize();
+            boolean changed = false;
+            for (int i=0; i<N; i++) {
+                Object cur = colGetEntry(i, 1);
+                if (collection.contains(cur)) {
+                    colRemoveAt(i);
+                    i--;
+                    N--;
+                    changed = true;
+                }
+            }
+            return changed;
+        }
+
+        @Override
+        public boolean retainAll(Collection<?> collection) {
+            int N = colGetSize();
+            boolean changed = false;
+            for (int i=0; i<N; i++) {
+                Object cur = colGetEntry(i, 1);
+                if (!collection.contains(cur)) {
+                    colRemoveAt(i);
+                    i--;
+                    N--;
+                    changed = true;
+                }
+            }
+            return changed;
+        }
+
+        @Override
+        public int size() {
+            return colGetSize();
+        }
+
+        @Override
+        public Object[] toArray() {
+            return toArrayHelper(1);
+        }
+
+        @Override
+        public <T> T[] toArray(T[] array) {
+            return toArrayHelper(array, 1);
+        }
+    };
+
+    public static <K, V> boolean containsAllHelper(Map<K, V> map, Collection<?> collection) {
+        Iterator<?> it = collection.iterator();
+        while (it.hasNext()) {
+            if (!map.containsKey(it.next())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static <K, V> boolean removeAllHelper(Map<K, V> map, Collection<?> collection) {
+        int oldSize = map.size();
+        Iterator<?> it = collection.iterator();
+        while (it.hasNext()) {
+            map.remove(it.next());
+        }
+        return oldSize != map.size();
+    }
+
+    public static <K, V> boolean retainAllHelper(Map<K, V> map, Collection<?> collection) {
+        int oldSize = map.size();
+        Iterator<K> it = map.keySet().iterator();
+        while (it.hasNext()) {
+            if (!collection.contains(it.next())) {
+                it.remove();
+            }
+        }
+        return oldSize != map.size();
+    }
+
+    public Object[] toArrayHelper(int offset) {
+        final int N = colGetSize();
+        Object[] result = new Object[N];
+        for (int i=0; i<N; i++) {
+            result[i] = colGetEntry(i, offset);
+        }
+        return result;
+    }
+
+    public <T> T[] toArrayHelper(T[] array, int offset) {
+        final int N  = colGetSize();
+        if (array.length < N) {
+            @SuppressWarnings("unchecked") T[] newArray
+                = (T[]) Array.newInstance(array.getClass().getComponentType(), N);
+            array = newArray;
+        }
+        for (int i=0; i<N; i++) {
+            array[i] = (T)colGetEntry(i, offset);
+        }
+        if (array.length > N) {
+            array[N] = null;
+        }
+        return array;
+    }
+
+    public static <T> boolean equalsSetHelper(Set<T> set, Object object) {
+        if (set == object) {
+            return true;
+        }
+        if (object instanceof Set) {
+            Set<?> s = (Set<?>) object;
+
+            try {
+                return set.size() == s.size() && set.containsAll(s);
+            } catch (NullPointerException ignored) {
+                return false;
+            } catch (ClassCastException ignored) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    public Set<Map.Entry<K, V>> getEntrySet() {
+        if (mEntrySet == null) {
+            mEntrySet = new EntrySet();
+        }
+        return mEntrySet;
+    }
+
+    public Set<K> getKeySet() {
+        if (mKeySet == null) {
+            mKeySet = new KeySet();
+        }
+        return mKeySet;
+    }
+
+    public Collection<V> getValues() {
+        if (mValues == null) {
+            mValues = new ValuesCollection();
+        }
+        return mValues;
+    }
+
+    protected abstract int colGetSize();
+    protected abstract Object colGetEntry(int index, int offset);
+    protected abstract int colIndexOfKey(Object key);
+    protected abstract int colIndexOfValue(Object key);
+    protected abstract Map<K, V> colGetMap();
+    protected abstract void colPut(K key, V value);
+    protected abstract V colSetValue(int index, V value);
+    protected abstract void colRemoveAt(int index);
+    protected abstract void colClear();
+}
diff --git a/android/util/MathUtils.java b/android/util/MathUtils.java
new file mode 100644
index 0000000..971e161
--- /dev/null
+++ b/android/util/MathUtils.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2009 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Rect;
+
+/**
+ * A class that contains utility methods related to numbers.
+ *
+ * @hide Pending API council approval
+ */
+public final class MathUtils {
+    private static final float DEG_TO_RAD = 3.1415926f / 180.0f;
+    private static final float RAD_TO_DEG = 180.0f / 3.1415926f;
+
+    private MathUtils() {
+    }
+
+    @UnsupportedAppUsage
+    public static float abs(float v) {
+        return v > 0 ? v : -v;
+    }
+
+    @UnsupportedAppUsage
+    public static int constrain(int amount, int low, int high) {
+        return amount < low ? low : (amount > high ? high : amount);
+    }
+
+    public static long constrain(long amount, long low, long high) {
+        return amount < low ? low : (amount > high ? high : amount);
+    }
+
+    @UnsupportedAppUsage
+    public static float constrain(float amount, float low, float high) {
+        return amount < low ? low : (amount > high ? high : amount);
+    }
+
+    public static float log(float a) {
+        return (float) Math.log(a);
+    }
+
+    public static float exp(float a) {
+        return (float) Math.exp(a);
+    }
+
+    public static float pow(float a, float b) {
+        return (float) Math.pow(a, b);
+    }
+
+    public static float sqrt(float a) {
+        return (float) Math.sqrt(a);
+    }
+
+    public static float max(float a, float b) {
+        return a > b ? a : b;
+    }
+
+    @UnsupportedAppUsage
+    public static float max(int a, int b) {
+        return a > b ? a : b;
+    }
+
+    public static float max(float a, float b, float c) {
+        return a > b ? (a > c ? a : c) : (b > c ? b : c);
+    }
+
+    public static float max(int a, int b, int c) {
+        return a > b ? (a > c ? a : c) : (b > c ? b : c);
+    }
+
+    public static float min(float a, float b) {
+        return a < b ? a : b;
+    }
+
+    public static float min(int a, int b) {
+        return a < b ? a : b;
+    }
+
+    public static float min(float a, float b, float c) {
+        return a < b ? (a < c ? a : c) : (b < c ? b : c);
+    }
+
+    public static float min(int a, int b, int c) {
+        return a < b ? (a < c ? a : c) : (b < c ? b : c);
+    }
+
+    public static float dist(float x1, float y1, float x2, float y2) {
+        final float x = (x2 - x1);
+        final float y = (y2 - y1);
+        return (float) Math.hypot(x, y);
+    }
+
+    public static float dist(float x1, float y1, float z1, float x2, float y2, float z2) {
+        final float x = (x2 - x1);
+        final float y = (y2 - y1);
+        final float z = (z2 - z1);
+        return (float) Math.sqrt(x * x + y * y + z * z);
+    }
+
+    public static float mag(float a, float b) {
+        return (float) Math.hypot(a, b);
+    }
+
+    public static float mag(float a, float b, float c) {
+        return (float) Math.sqrt(a * a + b * b + c * c);
+    }
+
+    public static float sq(float v) {
+        return v * v;
+    }
+
+    public static float dot(float v1x, float v1y, float v2x, float v2y) {
+        return v1x * v2x + v1y * v2y;
+    }
+
+    public static float cross(float v1x, float v1y, float v2x, float v2y) {
+        return v1x * v2y - v1y * v2x;
+    }
+
+    public static float radians(float degrees) {
+        return degrees * DEG_TO_RAD;
+    }
+
+    public static float degrees(float radians) {
+        return radians * RAD_TO_DEG;
+    }
+
+    public static float acos(float value) {
+        return (float) Math.acos(value);
+    }
+
+    public static float asin(float value) {
+        return (float) Math.asin(value);
+    }
+
+    public static float atan(float value) {
+        return (float) Math.atan(value);
+    }
+
+    public static float atan2(float a, float b) {
+        return (float) Math.atan2(a, b);
+    }
+
+    public static float tan(float angle) {
+        return (float) Math.tan(angle);
+    }
+
+    @UnsupportedAppUsage
+    public static float lerp(float start, float stop, float amount) {
+        return start + (stop - start) * amount;
+    }
+
+    /**
+     * Returns the interpolation scalar (s) that satisfies the equation: {@code value = }{@link
+     * #lerp}{@code (a, b, s)}
+     *
+     * <p>If {@code a == b}, then this function will return 0.
+     */
+    public static float lerpInv(float a, float b, float value) {
+        return a != b ? ((value - a) / (b - a)) : 0.0f;
+    }
+
+    /** Returns the single argument constrained between [0.0, 1.0]. */
+    public static float saturate(float value) {
+        return constrain(value, 0.0f, 1.0f);
+    }
+
+    /** Returns the saturated (constrained between [0, 1]) result of {@link #lerpInv}. */
+    public static float lerpInvSat(float a, float b, float value) {
+        return saturate(lerpInv(a, b, value));
+    }
+
+    /**
+     * Returns an interpolated angle in degrees between a set of start and end
+     * angles.
+     * <p>
+     * Unlike {@link #lerp(float, float, float)}, the direction and distance of
+     * travel is determined by the shortest angle between the start and end
+     * angles. For example, if the starting angle is 0 and the ending angle is
+     * 350, then the interpolated angle will be in the range [0,-10] rather
+     * than [0,350].
+     *
+     * @param start the starting angle in degrees
+     * @param end the ending angle in degrees
+     * @param amount the position between start and end in the range [0,1]
+     *               where 0 is the starting angle and 1 is the ending angle
+     * @return the interpolated angle in degrees
+     */
+    public static float lerpDeg(float start, float end, float amount) {
+        final float minAngle = (((end - start) + 180) % 360) - 180;
+        return minAngle * amount + start;
+    }
+
+    public static float norm(float start, float stop, float value) {
+        return (value - start) / (stop - start);
+    }
+
+    public static float map(float minStart, float minStop, float maxStart, float maxStop, float value) {
+        return maxStart + (maxStop - maxStart) * ((value - minStart) / (minStop - minStart));
+    }
+
+    /**
+     * Calculates a value in [rangeMin, rangeMax] that maps value in [valueMin, valueMax] to
+     * returnVal in [rangeMin, rangeMax].
+     * <p>
+     * Always returns a constrained value in the range [rangeMin, rangeMax], even if value is
+     * outside [valueMin, valueMax].
+     * <p>
+     * Eg:
+     *    constrainedMap(0f, 100f, 0f, 1f, 0.5f) = 50f
+     *    constrainedMap(20f, 200f, 10f, 20f, 20f) = 200f
+     *    constrainedMap(20f, 200f, 10f, 20f, 50f) = 200f
+     *    constrainedMap(10f, 50f, 10f, 20f, 5f) = 10f
+     *
+     * @param rangeMin minimum of the range that should be returned.
+     * @param rangeMax maximum of the range that should be returned.
+     * @param valueMin minimum of range to map {@code value} to.
+     * @param valueMax maximum of range to map {@code value} to.
+     * @param value to map to the range [{@code valueMin}, {@code valueMax}]. Note, can be outside
+     *              this range, resulting in a clamped value.
+     * @return the mapped value, constrained to [{@code rangeMin}, {@code rangeMax}.
+     */
+    public static float constrainedMap(
+            float rangeMin, float rangeMax, float valueMin, float valueMax, float value) {
+        return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value));
+    }
+
+    /**
+     * Perform Hermite interpolation between two values.
+     * Eg:
+     *   smoothStep(0, 0.5f, 0.5f) = 1f
+     *   smoothStep(0, 0.5f, 0.25f) = 0.5f
+     *
+     * @param start Left edge.
+     * @param end Right edge.
+     * @param x A value between {@code start} and {@code end}.
+     * @return A number between 0 and 1 representing where {@code x} is in the interpolation.
+     */
+    public static float smoothStep(float start, float end, float x) {
+        return constrain((x - start) / (end - start), 0f, 1f);
+    }
+
+    /**
+     * Returns the sum of the two parameters, or throws an exception if the resulting sum would
+     * cause an overflow or underflow.
+     * @throws IllegalArgumentException when overflow or underflow would occur.
+     */
+    public static int addOrThrow(int a, int b) throws IllegalArgumentException {
+        if (b == 0) {
+            return a;
+        }
+
+        if (b > 0 && a <= (Integer.MAX_VALUE - b)) {
+            return a + b;
+        }
+
+        if (b < 0 && a >= (Integer.MIN_VALUE - b)) {
+            return a + b;
+        }
+        throw new IllegalArgumentException("Addition overflow: " + a + " + " + b);
+    }
+
+    /**
+     * Resize a {@link Rect} so one size would be {@param largestSide}.
+     *
+     * @param outToResize Rectangle that will be resized.
+     * @param largestSide Size of the largest side.
+     */
+    public static void fitRect(Rect outToResize, int largestSide) {
+        if (outToResize.isEmpty()) {
+            return;
+        }
+        float maxSize = Math.max(outToResize.width(), outToResize.height());
+        outToResize.scale(largestSide / maxSize);
+    }
+}
diff --git a/android/util/MemoryIntArray.java b/android/util/MemoryIntArray.java
new file mode 100644
index 0000000..7d287e3
--- /dev/null
+++ b/android/util/MemoryIntArray.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import libcore.io.IoUtils;
+import dalvik.system.CloseGuard;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * This class is an array of integers that is backed by shared memory.
+ * It is useful for efficiently sharing state between processes. The
+ * write and read operations are guaranteed to not result in read/
+ * write memory tear, i.e. they are atomic. However, multiple read/
+ * write operations are <strong>not</strong> synchronized between
+ * each other.
+ * <p>
+ * The data structure is designed to have one owner process that can
+ * read/write. There may be multiple client processes that can only read.
+ * The owner process is the process that created the array. The shared
+ * memory is pinned (not reclaimed by the system) until the owning process
+ * dies or the data structure is closed. This class is <strong>not</strong>
+ * thread safe. You should not interact with an instance of this class
+ * once it is closed. If you pass back to the owner process an instance
+ * it will be read only even in the owning process.
+ * </p>
+ *
+ * @hide
+ */
+public final class MemoryIntArray implements Parcelable, Closeable {
+    private static final String TAG = "MemoryIntArray";
+
+    private static final int MAX_SIZE = 1024;
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private final boolean mIsOwner;
+    private final long mMemoryAddr;
+    private int mFd = -1;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param size The size of the array in terms of integer slots. Cannot be
+     *     more than {@link #getMaxSize()}.
+     * @throws IOException If an error occurs while accessing the shared memory.
+     */
+    public MemoryIntArray(int size) throws IOException {
+        if (size > MAX_SIZE) {
+            throw new IllegalArgumentException("Max size is " + MAX_SIZE);
+        }
+        mIsOwner = true;
+        final String name = UUID.randomUUID().toString();
+        mFd = nativeCreate(name, size);
+        mMemoryAddr = nativeOpen(mFd, mIsOwner);
+        mCloseGuard.open("close");
+    }
+
+    private MemoryIntArray(Parcel parcel) throws IOException {
+        mIsOwner = false;
+        ParcelFileDescriptor pfd = parcel.readParcelable(null);
+        if (pfd == null) {
+            throw new IOException("No backing file descriptor");
+        }
+        mFd = pfd.detachFd();
+        mMemoryAddr = nativeOpen(mFd, mIsOwner);
+        mCloseGuard.open("close");
+    }
+
+    /**
+     * @return Gets whether this array is mutable.
+     */
+    public boolean isWritable() {
+        enforceNotClosed();
+        return mIsOwner;
+    }
+
+    /**
+     * Gets the value at a given index.
+     *
+     * @param index The index.
+     * @return The value at this index.
+     * @throws IOException If an error occurs while accessing the shared memory.
+     */
+    public int get(int index) throws IOException {
+        enforceNotClosed();
+        enforceValidIndex(index);
+        return nativeGet(mFd, mMemoryAddr, index);
+    }
+
+    /**
+     * Sets the value at a given index. This method can be called only if
+     * {@link #isWritable()} returns true which means your process is the
+     * owner.
+     *
+     * @param index The index.
+     * @param value The value to set.
+     * @throws IOException If an error occurs while accessing the shared memory.
+     */
+    public void set(int index, int value) throws IOException {
+        enforceNotClosed();
+        enforceWritable();
+        enforceValidIndex(index);
+        nativeSet(mFd, mMemoryAddr, index, value);
+    }
+
+    /**
+     * Gets the array size.
+     *
+     * @throws IOException If an error occurs while accessing the shared memory.
+     */
+    public int size() throws IOException {
+        enforceNotClosed();
+        return nativeSize(mFd);
+    }
+
+    /**
+     * Closes the array releasing resources.
+     *
+     * @throws IOException If an error occurs while accessing the shared memory.
+     */
+    @Override
+    public void close() throws IOException {
+        if (!isClosed()) {
+            nativeClose(mFd, mMemoryAddr, mIsOwner);
+            mFd = -1;
+            mCloseGuard.close();
+        }
+    }
+
+    /**
+     * @return Whether this array is closed and shouldn't be used.
+     */
+    public boolean isClosed() {
+        return mFd == -1;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+
+            IoUtils.closeQuietly(this);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return CONTENTS_FILE_DESCRIPTOR;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(mFd)) {
+            parcel.writeParcelable(pfd, flags);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        MemoryIntArray other = (MemoryIntArray) obj;
+        return mFd == other.mFd;
+    }
+
+    @Override
+    public int hashCode() {
+        return mFd;
+    }
+
+    private void enforceNotClosed() {
+        if (isClosed()) {
+            throw new IllegalStateException("cannot interact with a closed instance");
+        }
+    }
+
+    private void enforceValidIndex(int index) throws IOException {
+        final int size = size();
+        if (index < 0 || index > size - 1) {
+            throw new IndexOutOfBoundsException(
+                    index + " not between 0 and " + (size - 1));
+        }
+    }
+
+    private void enforceWritable() {
+        if (!isWritable()) {
+            throw new UnsupportedOperationException("array is not writable");
+        }
+    }
+
+    private native int nativeCreate(String name, int size);
+    private native long nativeOpen(int fd, boolean owner);
+    private native void nativeClose(int fd, long memoryAddr, boolean owner);
+    private native int nativeGet(int fd, long memoryAddr, int index);
+    private native void nativeSet(int fd, long memoryAddr, int index, int value);
+    private native int nativeSize(int fd);
+
+    /**
+     * @return The max array size.
+     */
+    public static int getMaxSize() {
+        return MAX_SIZE;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<MemoryIntArray> CREATOR =
+            new Parcelable.Creator<MemoryIntArray>() {
+        @Override
+        public MemoryIntArray createFromParcel(Parcel parcel) {
+            try {
+                return new MemoryIntArray(parcel);
+            } catch (IOException ioe) {
+                throw new IllegalArgumentException("Error unparceling MemoryIntArray");
+            }
+        }
+
+        @Override
+        public MemoryIntArray[] newArray(int size) {
+            return new MemoryIntArray[size];
+        }
+    };
+}
diff --git a/android/util/MergedConfiguration.java b/android/util/MergedConfiguration.java
new file mode 100644
index 0000000..2399ada
--- /dev/null
+++ b/android/util/MergedConfiguration.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.PrintWriter;
+
+/**
+ * Container that holds global and override config and their merge product.
+ * Merged configuration updates automatically whenever global or override configs are updated via
+ * setters.
+ *
+ * {@hide}
+ */
+public class MergedConfiguration implements Parcelable {
+
+    private Configuration mGlobalConfig = new Configuration();
+    private Configuration mOverrideConfig = new Configuration();
+    private Configuration mMergedConfig = new Configuration();
+
+    public MergedConfiguration() {
+    }
+
+    public MergedConfiguration(Configuration globalConfig, Configuration overrideConfig) {
+        setConfiguration(globalConfig, overrideConfig);
+    }
+
+    public MergedConfiguration(Configuration globalConfig) {
+        setGlobalConfiguration(globalConfig);
+    }
+
+    public MergedConfiguration(MergedConfiguration mergedConfiguration) {
+        setConfiguration(mergedConfiguration.getGlobalConfiguration(),
+                mergedConfiguration.getOverrideConfiguration());
+    }
+
+    private MergedConfiguration(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mGlobalConfig, flags);
+        dest.writeParcelable(mOverrideConfig, flags);
+        dest.writeParcelable(mMergedConfig, flags);
+    }
+
+    public void readFromParcel(Parcel source) {
+        mGlobalConfig = source.readParcelable(Configuration.class.getClassLoader());
+        mOverrideConfig = source.readParcelable(Configuration.class.getClassLoader());
+        mMergedConfig = source.readParcelable(Configuration.class.getClassLoader());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Creator<MergedConfiguration> CREATOR = new Creator<MergedConfiguration>() {
+        @Override
+        public MergedConfiguration createFromParcel(Parcel in) {
+            return new MergedConfiguration(in);
+        }
+
+        @Override
+        public MergedConfiguration[] newArray(int size) {
+            return new MergedConfiguration[size];
+        }
+    };
+
+    /**
+     * Update global and override configurations.
+     * Merged configuration will automatically be updated.
+     * @param globalConfig New global configuration.
+     * @param overrideConfig New override configuration.
+     */
+    public void setConfiguration(Configuration globalConfig, Configuration overrideConfig) {
+        mGlobalConfig.setTo(globalConfig);
+        mOverrideConfig.setTo(overrideConfig);
+        updateMergedConfig();
+    }
+
+    /**
+     * Update global configurations.
+     * Merged configuration will automatically be updated.
+     * @param globalConfig New global configuration.
+     */
+    public void setGlobalConfiguration(Configuration globalConfig) {
+        mGlobalConfig.setTo(globalConfig);
+        updateMergedConfig();
+    }
+
+    /**
+     * Update override configurations.
+     * Merged configuration will automatically be updated.
+     * @param overrideConfig New override configuration.
+     */
+    public void setOverrideConfiguration(Configuration overrideConfig) {
+        mOverrideConfig.setTo(overrideConfig);
+        updateMergedConfig();
+    }
+
+    public void setTo(MergedConfiguration config) {
+        setConfiguration(config.mGlobalConfig, config.mOverrideConfig);
+    }
+
+    public void unset() {
+        mGlobalConfig.unset();
+        mOverrideConfig.unset();
+        updateMergedConfig();
+    }
+
+    /**
+     * @return Stored global configuration value.
+     */
+    @NonNull
+    public Configuration getGlobalConfiguration() {
+        return mGlobalConfig;
+    }
+
+    /**
+     * @return Stored override configuration value.
+     */
+    public Configuration getOverrideConfiguration() {
+        return mOverrideConfig;
+    }
+
+    /**
+     * @return Stored merged configuration value.
+     */
+    public Configuration getMergedConfiguration() {
+        return mMergedConfig;
+    }
+
+    /** Update merged config when global or override config changes. */
+    private void updateMergedConfig() {
+        mMergedConfig.setTo(mGlobalConfig);
+        mMergedConfig.updateFrom(mOverrideConfig);
+    }
+
+    @Override
+    public String toString() {
+        return "{mGlobalConfig=" + mGlobalConfig + " mOverrideConfig=" + mOverrideConfig + "}";
+    }
+
+    @Override
+    public int hashCode() {
+        return mMergedConfig.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object that) {
+        if (!(that instanceof MergedConfiguration)) {
+            return false;
+        }
+
+        if (that == this) return true;
+        return mMergedConfig.equals(((MergedConfiguration) that).mMergedConfig);
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "mGlobalConfig=" + mGlobalConfig);
+        pw.println(prefix + "mOverrideConfig=" + mOverrideConfig);
+    }
+}
diff --git a/android/util/MonthDisplayHelper.java b/android/util/MonthDisplayHelper.java
new file mode 100644
index 0000000..c3f13fc
--- /dev/null
+++ b/android/util/MonthDisplayHelper.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.Calendar;
+
+/**
+ * Helps answer common questions that come up when displaying a month in a
+ * 6 row calendar grid format.
+ *
+ * Not thread safe.
+ */
+public class MonthDisplayHelper {
+
+    // display pref
+    private final int mWeekStartDay;
+
+    // holds current month, year, helps compute display
+    private Calendar mCalendar;
+
+    // cached computed stuff that helps with display
+    private int mNumDaysInMonth;
+    private int mNumDaysInPrevMonth;
+    private int mOffset;
+
+
+    /**
+     * @param year The year.
+     * @param month The month.
+     * @param weekStartDay What day of the week the week should start.
+     */
+    public MonthDisplayHelper(int year, int month, int weekStartDay) {
+
+        if (weekStartDay < Calendar.SUNDAY || weekStartDay > Calendar.SATURDAY) {
+            throw new IllegalArgumentException();
+        }
+        mWeekStartDay = weekStartDay;
+
+        mCalendar = Calendar.getInstance();
+        mCalendar.set(Calendar.YEAR, year);
+        mCalendar.set(Calendar.MONTH, month);
+        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
+        mCalendar.set(Calendar.HOUR_OF_DAY, 0);
+        mCalendar.set(Calendar.MINUTE, 0);
+        mCalendar.set(Calendar.SECOND, 0);
+        mCalendar.getTimeInMillis();
+
+        recalculate();
+    }
+
+
+    public MonthDisplayHelper(int year, int month) {
+        this(year, month, Calendar.SUNDAY);
+    }
+
+
+    public int getYear() {
+        return mCalendar.get(Calendar.YEAR);
+    }
+
+    public int getMonth() {
+        return mCalendar.get(Calendar.MONTH);
+    }
+
+
+    public int getWeekStartDay() {
+        return mWeekStartDay;
+    }
+
+    /**
+     * @return The first day of the month using a constants such as
+     *   {@link java.util.Calendar#SUNDAY}.
+     */
+    public int getFirstDayOfMonth() {
+        return mCalendar.get(Calendar.DAY_OF_WEEK);
+    }
+
+    /**
+     * @return The number of days in the month.
+     */
+    public int getNumberOfDaysInMonth() {
+        return mNumDaysInMonth;
+    }
+
+
+    /**
+     * @return The offset from displaying everything starting on the very first
+     *   box.  For example, if the calendar is set to display the first day of
+     *   the week as Sunday, and the month starts on a Wednesday, the offset is 3.
+     */
+    public int getOffset() {
+        return mOffset;
+    }
+
+
+    /**
+     * @param row Which row (0-5).
+     * @return the digits of the month to display in one
+     * of the 6 rows of a calendar month display.
+     */
+    public int[] getDigitsForRow(int row) {
+        if (row < 0 || row > 5) {
+            throw new IllegalArgumentException("row " + row
+                    + " out of range (0-5)");
+        }
+
+        int [] result = new int[7];
+        for (int column = 0; column < 7; column++) {
+            result[column] = getDayAt(row, column);
+        }
+
+        return result;
+    }
+
+    /**
+     * @param row The row, 0-5, starting from the top.
+     * @param column The column, 0-6, starting from the left.
+     * @return The day at a particular row, column
+     */
+    public int getDayAt(int row, int column) {
+
+        if (row == 0 && column < mOffset) {
+            return mNumDaysInPrevMonth + column - mOffset + 1;
+        }
+
+        int day = 7 * row + column - mOffset + 1;
+
+        return (day > mNumDaysInMonth) ?
+                day - mNumDaysInMonth : day;
+    }
+
+    /**
+     * @return Which row day is in.
+     */
+    public int getRowOf(int day) {
+        return (day + mOffset - 1) / 7;
+    }
+
+    /**
+     * @return Which column day is in.
+     */
+    public int getColumnOf(int day) {
+        return (day + mOffset - 1) % 7;
+    }
+
+    /**
+     * Decrement the month.
+     */
+    public void previousMonth() {
+        mCalendar.add(Calendar.MONTH, -1);
+        recalculate();
+    }
+
+    /**
+     * Increment the month.
+     */
+    public void nextMonth() {
+        mCalendar.add(Calendar.MONTH, 1);
+        recalculate();
+    }
+
+    /**
+     * @return Whether the row and column fall within the month.
+     */
+    public boolean isWithinCurrentMonth(int row, int column) {
+
+        if (row < 0 || column < 0 || row > 5 || column > 6) {
+            return false;
+        }
+
+        if (row == 0 && column < mOffset) {
+            return false;
+        }
+
+        int day = 7 * row + column - mOffset + 1;
+        if (day > mNumDaysInMonth) {
+            return false;
+        }
+        return true;
+    }
+
+
+    // helper method that recalculates cached values based on current month / year
+    private void recalculate() {
+
+        mNumDaysInMonth = mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+
+        mCalendar.add(Calendar.MONTH, -1);
+        mNumDaysInPrevMonth = mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+        mCalendar.add(Calendar.MONTH, 1);
+
+        int firstDayOfMonth = getFirstDayOfMonth();
+        int offset = firstDayOfMonth - mWeekStartDay;
+        if (offset < 0) {
+            offset += 7;
+        }
+        mOffset = offset;
+    }
+}
diff --git a/android/util/MutableBoolean.java b/android/util/MutableBoolean.java
new file mode 100644
index 0000000..44e73cc
--- /dev/null
+++ b/android/util/MutableBoolean.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.util;
+
+/**
+ * @deprecated This class will be removed from a future version of the Android API.
+ */
+@Deprecated
+public final class MutableBoolean {
+    public boolean value;
+
+    public MutableBoolean(boolean value) {
+        this.value = value;
+    }
+}
diff --git a/android/util/MutableByte.java b/android/util/MutableByte.java
new file mode 100644
index 0000000..b9ec25d
--- /dev/null
+++ b/android/util/MutableByte.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.util;
+
+/**
+ * @deprecated This class will be removed from a future version of the Android API.
+ */
+@Deprecated
+public final class MutableByte {
+    public byte value;
+
+    public MutableByte(byte value) {
+        this.value = value;
+    }
+}
diff --git a/android/util/MutableChar.java b/android/util/MutableChar.java
new file mode 100644
index 0000000..9f7a9ae
--- /dev/null
+++ b/android/util/MutableChar.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.util;
+
+/**
+ * @deprecated This class will be removed from a future version of the Android API.
+ */
+@Deprecated
+public final class MutableChar {
+    public char value;
+
+    public MutableChar(char value) {
+        this.value = value;
+    }
+}
diff --git a/android/util/MutableDouble.java b/android/util/MutableDouble.java
new file mode 100644
index 0000000..56e539b
--- /dev/null
+++ b/android/util/MutableDouble.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.util;
+
+/**
+ * @deprecated This class will be removed from a future version of the Android API.
+ */
+@Deprecated
+public final class MutableDouble {
+    public double value;
+
+    public MutableDouble(double value) {
+        this.value = value;
+    }
+}
diff --git a/android/util/MutableFloat.java b/android/util/MutableFloat.java
new file mode 100644
index 0000000..6d7ad59
--- /dev/null
+++ b/android/util/MutableFloat.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.util;
+
+/**
+ * @deprecated This class will be removed from a future version of the Android API.
+ */
+@Deprecated
+public final class MutableFloat {
+    public float value;
+
+    public MutableFloat(float value) {
+        this.value = value;
+    }
+}
diff --git a/android/util/MutableInt.java b/android/util/MutableInt.java
new file mode 100644
index 0000000..bb24566
--- /dev/null
+++ b/android/util/MutableInt.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.util;
+
+/**
+ * @deprecated This class will be removed from a future version of the Android API.
+ */
+@Deprecated
+public final class MutableInt {
+    public int value;
+
+    public MutableInt(int value) {
+        this.value = value;
+    }
+}
diff --git a/android/util/MutableLong.java b/android/util/MutableLong.java
new file mode 100644
index 0000000..86e70e1
--- /dev/null
+++ b/android/util/MutableLong.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.util;
+
+/**
+ * @deprecated This class will be removed from a future version of the Android API.
+ */
+@Deprecated
+public final class MutableLong {
+    public long value;
+
+    public MutableLong(long value) {
+        this.value = value;
+    }
+}
diff --git a/android/util/MutableShort.java b/android/util/MutableShort.java
new file mode 100644
index 0000000..b94ab07
--- /dev/null
+++ b/android/util/MutableShort.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.util;
+
+/**
+ * @deprecated This class will be removed from a future version of the Android API.
+ */
+@Deprecated
+public final class MutableShort {
+    public short value;
+
+    public MutableShort(short value) {
+        this.value = value;
+    }
+}
diff --git a/android/util/NoSuchPropertyException.java b/android/util/NoSuchPropertyException.java
new file mode 100644
index 0000000..b93f983
--- /dev/null
+++ b/android/util/NoSuchPropertyException.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.util;
+
+/**
+ * Thrown when code requests a {@link Property} on a class that does
+ * not expose the appropriate method or field.
+ *
+ * @see Property#of(java.lang.Class, java.lang.Class, java.lang.String)
+ */
+public class NoSuchPropertyException extends RuntimeException {
+
+    public NoSuchPropertyException(String s) {
+        super(s);
+    }
+
+}
diff --git a/android/util/NtpTrustedTime.java b/android/util/NtpTrustedTime.java
new file mode 100644
index 0000000..0892c94
--- /dev/null
+++ b/android/util/NtpTrustedTime.java
@@ -0,0 +1,296 @@
+/*
+ * 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 android.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.SntpClient;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+/**
+ * A singleton that connects with a remote NTP server as its trusted time source. This class
+ * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the
+ * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()}
+ * will block during that request.
+ *
+ * @hide
+ */
+public class NtpTrustedTime implements TrustedTime {
+
+    /**
+     * The result of a successful NTP query.
+     *
+     * @hide
+     */
+    public static class TimeResult {
+        private final long mTimeMillis;
+        private final long mElapsedRealtimeMillis;
+        private final long mCertaintyMillis;
+
+        public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) {
+            mTimeMillis = timeMillis;
+            mElapsedRealtimeMillis = elapsedRealtimeMillis;
+            mCertaintyMillis = certaintyMillis;
+        }
+
+        public long getTimeMillis() {
+            return mTimeMillis;
+        }
+
+        public long getElapsedRealtimeMillis() {
+            return mElapsedRealtimeMillis;
+        }
+
+        public long getCertaintyMillis() {
+            return mCertaintyMillis;
+        }
+
+        /** Calculates and returns the current time accounting for the age of this result. */
+        public long currentTimeMillis() {
+            return mTimeMillis + getAgeMillis();
+        }
+
+        /** Calculates and returns the age of this result. */
+        public long getAgeMillis() {
+            return SystemClock.elapsedRealtime() - mElapsedRealtimeMillis;
+        }
+
+        @Override
+        public String toString() {
+            return "TimeResult{"
+                    + "mTimeMillis=" + mTimeMillis
+                    + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+                    + ", mCertaintyMillis=" + mCertaintyMillis
+                    + '}';
+        }
+    }
+
+    private static final String TAG = "NtpTrustedTime";
+    private static final boolean LOGD = false;
+
+    private static NtpTrustedTime sSingleton;
+
+    @NonNull
+    private final Context mContext;
+
+    /**
+     * A supplier that returns the ConnectivityManager. The Supplier can return null if
+     * ConnectivityService isn't running yet.
+     */
+    private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
+            new Supplier<ConnectivityManager>() {
+        private ConnectivityManager mConnectivityManager;
+
+        @Nullable
+        @Override
+        public synchronized ConnectivityManager get() {
+            // We can't do this at initialization time: ConnectivityService might not be running
+            // yet.
+            if (mConnectivityManager == null) {
+                mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+            }
+            return mConnectivityManager;
+        }
+    };
+
+    // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during
+    // forceRefresh().
+    private volatile TimeResult mTimeResult;
+
+    private NtpTrustedTime(Context context) {
+        mContext = Objects.requireNonNull(context);
+    }
+
+    @UnsupportedAppUsage
+    public static synchronized NtpTrustedTime getInstance(Context context) {
+        if (sSingleton == null) {
+            Context appContext = context.getApplicationContext();
+            sSingleton = new NtpTrustedTime(appContext);
+        }
+        return sSingleton;
+    }
+
+    @UnsupportedAppUsage
+    public boolean forceRefresh() {
+        synchronized (this) {
+            NtpConnectionInfo connectionInfo = getNtpConnectionInfo();
+            if (connectionInfo == null) {
+                // missing server config, so no trusted time available
+                if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
+                return false;
+            }
+
+            ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
+            if (connectivityManager == null) {
+                if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");
+                return false;
+            }
+            final Network network = connectivityManager.getActiveNetwork();
+            final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+            if (ni == null || !ni.isConnected()) {
+                if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
+                return false;
+            }
+
+            if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
+            final SntpClient client = new SntpClient();
+            final String serverName = connectionInfo.getServer();
+            final int timeoutMillis = connectionInfo.getTimeoutMillis();
+            if (client.requestTime(serverName, timeoutMillis, network)) {
+                long ntpCertainty = client.getRoundTripTime() / 2;
+                mTimeResult = new TimeResult(
+                        client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty);
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public boolean hasCache() {
+        return mTimeResult != null;
+    }
+
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
+    @Override
+    public long getCacheAge() {
+        TimeResult timeResult = mTimeResult;
+        if (timeResult != null) {
+            return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis();
+        } else {
+            return Long.MAX_VALUE;
+        }
+    }
+
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public long currentTimeMillis() {
+        TimeResult timeResult = mTimeResult;
+        if (timeResult == null) {
+            throw new IllegalStateException("Missing authoritative time source");
+        }
+        if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
+
+        // current time is age after the last ntp cache; callers who
+        // want fresh values will hit forceRefresh() first.
+        return timeResult.currentTimeMillis();
+    }
+
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public long getCachedNtpTime() {
+        if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
+        TimeResult timeResult = mTimeResult;
+        return timeResult == null ? 0 : timeResult.getTimeMillis();
+    }
+
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public long getCachedNtpTimeReference() {
+        TimeResult timeResult = mTimeResult;
+        return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis();
+    }
+
+    /**
+     * Returns an object containing the latest NTP information available. Can return {@code null} if
+     * no information is available.
+     */
+    @Nullable
+    public TimeResult getCachedTimeResult() {
+        return mTimeResult;
+    }
+
+    private static class NtpConnectionInfo {
+
+        @NonNull private final String mServer;
+        private final int mTimeoutMillis;
+
+        NtpConnectionInfo(@NonNull String server, int timeoutMillis) {
+            mServer = Objects.requireNonNull(server);
+            mTimeoutMillis = timeoutMillis;
+        }
+
+        @NonNull
+        public String getServer() {
+            return mServer;
+        }
+
+        int getTimeoutMillis() {
+            return mTimeoutMillis;
+        }
+    }
+
+    @GuardedBy("this")
+    private NtpConnectionInfo getNtpConnectionInfo() {
+        final ContentResolver resolver = mContext.getContentResolver();
+
+        final Resources res = mContext.getResources();
+        final String defaultServer = res.getString(
+                com.android.internal.R.string.config_ntpServer);
+        final int defaultTimeoutMillis = res.getInteger(
+                com.android.internal.R.integer.config_ntpTimeout);
+
+        final String secureServer = Settings.Global.getString(
+                resolver, Settings.Global.NTP_SERVER);
+        final int timeoutMillis = Settings.Global.getInt(
+                resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);
+
+        final String server = secureServer != null ? secureServer : defaultServer;
+        return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis);
+    }
+}
diff --git a/android/util/PackageUtils.java b/android/util/PackageUtils.java
new file mode 100644
index 0000000..8061bf3
--- /dev/null
+++ b/android/util/PackageUtils.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.Signature;
+
+import libcore.util.HexEncoding;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * Helper functions applicable to packages.
+ * @hide
+ */
+public final class PackageUtils {
+
+    private PackageUtils() {
+        /* hide constructor */
+    }
+
+    /**
+     * Computes the SHA256 digests of a list of signatures. Items in the
+     * resulting array of hashes correspond to the signatures in the
+     * input array.
+     * @param signatures The signatures.
+     * @return The digest array.
+     */
+    public static @NonNull String[] computeSignaturesSha256Digests(
+            @NonNull Signature[] signatures) {
+        final int signatureCount = signatures.length;
+        final String[] digests = new String[signatureCount];
+        for (int i = 0; i < signatureCount; i++) {
+            digests[i] = computeSha256Digest(signatures[i].toByteArray());
+        }
+        return digests;
+    }
+    /**
+     * Computes a SHA256 digest of the signatures' SHA256 digests. First,
+     * individual hashes for each signature is derived in a hexademical
+     * form, then these strings are sorted based the natural ordering, and
+     * finally a hash is derived from these strings' bytes.
+     * @param signatures The signatures.
+     * @return The digest.
+     */
+    public static @NonNull String computeSignaturesSha256Digest(
+            @NonNull Signature[] signatures) {
+        // Shortcut for optimization - most apps singed by a single cert
+        if (signatures.length == 1) {
+            return computeSha256Digest(signatures[0].toByteArray());
+        }
+
+        // Make sure these are sorted to handle reversed certificates
+        final String[] sha256Digests = computeSignaturesSha256Digests(signatures);
+        return computeSignaturesSha256Digest(sha256Digests);
+    }
+
+    /**
+     * Computes a SHA256 digest in of the signatures SHA256 digests. First,
+     * the strings are sorted based the natural ordering, and then a hash is
+     * derived from these strings' bytes.
+     * @param sha256Digests Signature SHA256 hashes in hexademical form.
+     * @return The digest.
+     */
+    public static @NonNull String computeSignaturesSha256Digest(
+            @NonNull String[] sha256Digests) {
+        // Shortcut for optimization - most apps singed by a single cert
+        if (sha256Digests.length == 1) {
+            return sha256Digests[0];
+        }
+
+        // Make sure these are sorted to handle reversed certificates
+        Arrays.sort(sha256Digests);
+
+        final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        for (String sha256Digest : sha256Digests) {
+            try {
+                bytes.write(sha256Digest.getBytes());
+            } catch (IOException e) {
+                /* ignore - can't happen */
+            }
+        }
+        return computeSha256Digest(bytes.toByteArray());
+    }
+
+    /**
+     * Computes the SHA256 digest of some data.
+     * @param data The data.
+     * @return The digest or null if an error occurs.
+     */
+    public static @Nullable byte[] computeSha256DigestBytes(@NonNull byte[] data) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA256");
+        } catch (NoSuchAlgorithmException e) {
+            /* can't happen */
+            return null;
+        }
+
+        messageDigest.update(data);
+
+        return messageDigest.digest();
+    }
+
+    /**
+     * Computes the SHA256 digest of some data.
+     * @param data The data.
+     * @return The digest or null if an error occurs.
+     */
+    public static @Nullable String computeSha256Digest(@NonNull byte[] data) {
+        byte[] sha256DigestBytes = computeSha256DigestBytes(data);
+        if (sha256DigestBytes == null) {
+            return null;
+        }
+        return HexEncoding.encodeToString(sha256DigestBytes, true /* uppercase */);
+    }
+}
diff --git a/android/util/Pair.java b/android/util/Pair.java
new file mode 100644
index 0000000..f96da72
--- /dev/null
+++ b/android/util/Pair.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2009 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;
+
+import java.util.Objects;
+
+/**
+ * Container to ease passing around a tuple of two objects. This object provides a sensible
+ * implementation of equals(), returning true if equals() is true on each of the contained
+ * objects.
+ */
+public class Pair<F, S> {
+    public final F first;
+    public final S second;
+
+    /**
+     * Constructor for a Pair.
+     *
+     * @param first the first object in the Pair
+     * @param second the second object in the pair
+     */
+    public Pair(F first, S second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    /**
+     * Checks the two objects for equality by delegating to their respective
+     * {@link Object#equals(Object)} methods.
+     *
+     * @param o the {@link Pair} to which this one is to be checked for equality
+     * @return true if the underlying objects of the Pair are both considered
+     *         equal
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Pair)) {
+            return false;
+        }
+        Pair<?, ?> p = (Pair<?, ?>) o;
+        return Objects.equals(p.first, first) && Objects.equals(p.second, second);
+    }
+
+    /**
+     * Compute a hash code using the hash codes of the underlying objects
+     *
+     * @return a hashcode of the Pair
+     */
+    @Override
+    public int hashCode() {
+        return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
+    }
+
+    @Override
+    public String toString() {
+        return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}";
+    }
+
+    /**
+     * Convenience method for creating an appropriately typed pair.
+     * @param a the first object in the Pair
+     * @param b the second object in the pair
+     * @return a Pair that is templatized with the types of a and b
+     */
+    public static <A, B> Pair <A, B> create(A a, B b) {
+        return new Pair<A, B>(a, b);
+    }
+}
diff --git a/android/util/PathParser.java b/android/util/PathParser.java
new file mode 100644
index 0000000..1e5ec0b
--- /dev/null
+++ b/android/util/PathParser.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.util;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Path;
+
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * @hide
+ */
+public class PathParser {
+    static final String LOGTAG = PathParser.class.getSimpleName();
+
+    /**
+     * @param pathString The string representing a path, the same as "d" string in svg file.
+     * @return the generated Path object.
+     */
+    @UnsupportedAppUsage
+    public static Path createPathFromPathData(String pathString) {
+        if (pathString == null) {
+            throw new IllegalArgumentException("Path string can not be null.");
+        }
+        Path path = new Path();
+        nParseStringForPath(path.mNativePath, pathString, pathString.length());
+        return path;
+    }
+
+    /**
+     * Interpret PathData as path commands and insert the commands to the given path.
+     *
+     * @param data The source PathData to be converted.
+     * @param outPath The Path object where path commands will be inserted.
+     */
+    public static void createPathFromPathData(Path outPath, PathData data) {
+        nCreatePathFromPathData(outPath.mNativePath, data.mNativePathData);
+    }
+
+    /**
+     * @param pathDataFrom The source path represented in PathData
+     * @param pathDataTo The target path represented in PathData
+     * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
+     */
+    public static boolean canMorph(PathData pathDataFrom, PathData pathDataTo) {
+        return nCanMorph(pathDataFrom.mNativePathData, pathDataTo.mNativePathData);
+    }
+
+    /**
+     * PathData class is a wrapper around the native PathData object, which contains
+     * the result of parsing a path string. Specifically, there are verbs and points
+     * associated with each verb stored in PathData. This data can then be used to
+     * generate commands to manipulate a Path.
+     */
+    public static class PathData {
+        long mNativePathData = 0;
+        public PathData() {
+            mNativePathData = nCreateEmptyPathData();
+        }
+
+        public PathData(PathData data) {
+            mNativePathData = nCreatePathData(data.mNativePathData);
+        }
+
+        public PathData(String pathString) {
+            mNativePathData = nCreatePathDataFromString(pathString, pathString.length());
+            if (mNativePathData == 0) {
+                throw new IllegalArgumentException("Invalid pathData: " + pathString);
+            }
+        }
+
+        public long getNativePtr() {
+            return mNativePathData;
+        }
+
+        /**
+         * Update the path data to match the source.
+         * Before calling this, make sure canMorph(target, source) is true.
+         *
+         * @param source The source path represented in PathData
+         */
+        public void setPathData(PathData source) {
+            nSetPathData(mNativePathData, source.mNativePathData);
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            if (mNativePathData != 0) {
+                nFinalize(mNativePathData);
+                mNativePathData = 0;
+            }
+            super.finalize();
+        }
+    }
+
+    /**
+     * Interpolate between the <code>fromData</code> and <code>toData</code> according to the
+     * <code>fraction</code>, and put the resulting path data into <code>outData</code>.
+     *
+     * @param outData The resulting PathData of the interpolation
+     * @param fromData The start value as a PathData.
+     * @param toData The end value as a PathData
+     * @param fraction The fraction to interpolate.
+     */
+    public static boolean interpolatePathData(PathData outData, PathData fromData, PathData toData,
+            float fraction) {
+        return nInterpolatePathData(outData.mNativePathData, fromData.mNativePathData,
+                toData.mNativePathData, fraction);
+    }
+
+    // Native functions are defined below.
+    private static native void nParseStringForPath(long pathPtr, String pathString,
+            int stringLength);
+    private static native long nCreatePathDataFromString(String pathString, int stringLength);
+
+    // ----------------- @FastNative -----------------------
+
+    @FastNative
+    private static native void nCreatePathFromPathData(long outPathPtr, long pathData);
+    @FastNative
+    private static native long nCreateEmptyPathData();
+    @FastNative
+    private static native long nCreatePathData(long nativePtr);
+    @FastNative
+    private static native boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
+            long toDataPtr, float fraction);
+    @FastNative
+    private static native void nFinalize(long nativePtr);
+    @FastNative
+    private static native boolean nCanMorph(long fromDataPtr, long toDataPtr);
+    @FastNative
+    private static native void nSetPathData(long outDataPtr, long fromDataPtr);
+}
+
+
diff --git a/android/util/PathParser_Delegate.java b/android/util/PathParser_Delegate.java
new file mode 100644
index 0000000..96c093c
--- /dev/null
+++ b/android/util/PathParser_Delegate.java
@@ -0,0 +1,846 @@
+/*
+ * Copyright (C) 2015 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.graphics.Path_Delegate;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Delegate that provides implementation for native methods in {@link android.util.PathParser}
+ * <p/>
+ * Through the layoutlib_create tool, selected methods of PathParser have been replaced by calls to
+ * methods of the same name in this delegate class.
+ *
+ * Most of the code has been taken from the implementation in
+ * {@code tools/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathParser.java}
+ * revision be6fe89a3b686db5a75e7e692a148699973957f3
+ */
+public class PathParser_Delegate {
+
+    private static final Logger LOGGER = Logger.getLogger("PathParser");
+
+    // ---- Builder delegate manager ----
+    private static final DelegateManager<PathParser_Delegate> sManager =
+            new DelegateManager<PathParser_Delegate>(PathParser_Delegate.class);
+
+    // ---- delegate data ----
+    @NonNull
+    private PathDataNode[] mPathDataNodes;
+
+    public static PathParser_Delegate getDelegate(long nativePtr) {
+        return sManager.getDelegate(nativePtr);
+    }
+
+    private PathParser_Delegate(@NonNull PathDataNode[] nodes) {
+        mPathDataNodes = nodes;
+    }
+
+    public PathDataNode[] getPathDataNodes() {
+        return mPathDataNodes;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nParseStringForPath(long pathPtr, @NonNull String pathString, int
+            stringLength) {
+        Path_Delegate path_delegate = Path_Delegate.getDelegate(pathPtr);
+        if (path_delegate == null) {
+            return;
+        }
+        assert pathString.length() == stringLength;
+        PathDataNode.nodesToPath(createNodesFromPathData(pathString), path_delegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCreatePathFromPathData(long outPathPtr, long pathData) {
+        Path_Delegate path_delegate = Path_Delegate.getDelegate(outPathPtr);
+        PathParser_Delegate source = sManager.getDelegate(outPathPtr);
+        if (source == null || path_delegate == null) {
+            return;
+        }
+        PathDataNode.nodesToPath(source.mPathDataNodes, path_delegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreateEmptyPathData() {
+        PathParser_Delegate newDelegate = new PathParser_Delegate(new PathDataNode[0]);
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreatePathData(long nativePtr) {
+        PathParser_Delegate source = sManager.getDelegate(nativePtr);
+        if (source == null) {
+            return 0;
+        }
+        PathParser_Delegate dest = new PathParser_Delegate(deepCopyNodes(source.mPathDataNodes));
+        return sManager.addNewDelegate(dest);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreatePathDataFromString(@NonNull String pathString,
+            int stringLength) {
+        assert pathString.length() == stringLength : "Inconsistent path string length.";
+        PathDataNode[] nodes = createNodesFromPathData(pathString);
+        PathParser_Delegate delegate = new PathParser_Delegate(nodes);
+        return sManager.addNewDelegate(delegate);
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
+            long toDataPtr, float fraction) {
+        PathParser_Delegate out = sManager.getDelegate(outDataPtr);
+        PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
+        PathParser_Delegate to = sManager.getDelegate(toDataPtr);
+        if (out == null || from == null || to == null) {
+            return false;
+        }
+        int length = from.mPathDataNodes.length;
+        if (length != to.mPathDataNodes.length) {
+            Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                    "Cannot interpolate path data with different lengths (from " + length + " to " +
+                            to.mPathDataNodes.length + ").", null, null);
+            return false;
+        }
+        if (out.mPathDataNodes.length != length) {
+            out.mPathDataNodes = new PathDataNode[length];
+        }
+        for (int i = 0; i < length; i++) {
+            if (out.mPathDataNodes[i] == null) {
+                out.mPathDataNodes[i] = new PathDataNode(from.mPathDataNodes[i]);
+            }
+            out.mPathDataNodes[i].interpolatePathDataNode(from.mPathDataNodes[i],
+                        to.mPathDataNodes[i], fraction);
+        }
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nFinalize(long nativePtr) {
+        sManager.removeJavaReferenceFor(nativePtr);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nCanMorph(long fromDataPtr, long toDataPtr) {
+        PathParser_Delegate fromPath = PathParser_Delegate.getDelegate(fromDataPtr);
+        PathParser_Delegate toPath = PathParser_Delegate.getDelegate(toDataPtr);
+        if (fromPath == null || toPath == null || fromPath.getPathDataNodes() == null || toPath
+                .getPathDataNodes() == null) {
+            return true;
+        }
+        return PathParser_Delegate.canMorph(fromPath.getPathDataNodes(), toPath.getPathDataNodes());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetPathData(long outDataPtr, long fromDataPtr) {
+        PathParser_Delegate out = sManager.getDelegate(outDataPtr);
+        PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
+        if (from == null || out == null) {
+            return;
+        }
+        out.mPathDataNodes = deepCopyNodes(from.mPathDataNodes);
+    }
+
+    /**
+     * @param pathData The string representing a path, the same as "d" string in svg file.
+     *
+     * @return an array of the PathDataNode.
+     */
+    @NonNull
+    public static PathDataNode[] createNodesFromPathData(@NonNull String pathData) {
+        int start = 0;
+        int end = 1;
+
+        ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
+        while (end < pathData.length()) {
+            end = nextStart(pathData, end);
+            String s = pathData.substring(start, end).trim();
+            if (s.length() > 0) {
+                float[] val = getFloats(s);
+                addNode(list, s.charAt(0), val);
+            }
+
+            start = end;
+            end++;
+        }
+        if ((end - start) == 1 && start < pathData.length()) {
+            addNode(list, pathData.charAt(start), new float[0]);
+        }
+        return list.toArray(new PathDataNode[list.size()]);
+    }
+
+    /**
+     * @param source The array of PathDataNode to be duplicated.
+     *
+     * @return a deep copy of the <code>source</code>.
+     */
+    @NonNull
+    public static PathDataNode[] deepCopyNodes(@NonNull PathDataNode[] source) {
+        PathDataNode[] copy = new PathDataNode[source.length];
+        for (int i = 0; i < source.length; i++) {
+            copy[i] = new PathDataNode(source[i]);
+        }
+        return copy;
+    }
+
+    /**
+     * @param nodesFrom The source path represented in an array of PathDataNode
+     * @param nodesTo The target path represented in an array of PathDataNode
+     * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
+     */
+    public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
+        if (nodesFrom == null || nodesTo == null) {
+            return false;
+        }
+
+        if (nodesFrom.length != nodesTo.length) {
+            return false;
+        }
+
+        for (int i = 0; i < nodesFrom.length; i ++) {
+            if (nodesFrom[i].mType != nodesTo[i].mType
+                    || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Update the target's data to match the source.
+     * Before calling this, make sure canMorph(target, source) is true.
+     *
+     * @param target The target path represented in an array of PathDataNode
+     * @param source The source path represented in an array of PathDataNode
+     */
+    public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
+        for (int i = 0; i < source.length; i ++) {
+            target[i].mType = source[i].mType;
+            for (int j = 0; j < source[i].mParams.length; j ++) {
+                target[i].mParams[j] = source[i].mParams[j];
+            }
+        }
+    }
+
+    private static int nextStart(@NonNull String s, int end) {
+        char c;
+
+        while (end < s.length()) {
+            c = s.charAt(end);
+            // Note that 'e' or 'E' are not valid path commands, but could be
+            // used for floating point numbers' scientific notation.
+            // Therefore, when searching for next command, we should ignore 'e'
+            // and 'E'.
+            if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
+                    && c != 'e' && c != 'E') {
+                return end;
+            }
+            end++;
+        }
+        return end;
+    }
+
+    /**
+     * Calculate the position of the next comma or space or negative sign
+     *
+     * @param s the string to search
+     * @param start the position to start searching
+     * @param result the result of the extraction, including the position of the the starting
+     * position of next number, whether it is ending with a '-'.
+     */
+    private static void extract(@NonNull String s, int start, @NonNull ExtractFloatResult result) {
+        // Now looking for ' ', ',', '.' or '-' from the start.
+        int currentIndex = start;
+        boolean foundSeparator = false;
+        result.mEndWithNegOrDot = false;
+        boolean secondDot = false;
+        boolean isExponential = false;
+        for (; currentIndex < s.length(); currentIndex++) {
+            boolean isPrevExponential = isExponential;
+            isExponential = false;
+            char currentChar = s.charAt(currentIndex);
+            switch (currentChar) {
+                case ' ':
+                case ',':
+                case '\t':
+                case '\n':
+                    foundSeparator = true;
+                    break;
+                case '-':
+                    // The negative sign following a 'e' or 'E' is not a separator.
+                    if (currentIndex != start && !isPrevExponential) {
+                        foundSeparator = true;
+                        result.mEndWithNegOrDot = true;
+                    }
+                    break;
+                case '.':
+                    if (!secondDot) {
+                        secondDot = true;
+                    } else {
+                        // This is the second dot, and it is considered as a separator.
+                        foundSeparator = true;
+                        result.mEndWithNegOrDot = true;
+                    }
+                    break;
+                case 'e':
+                case 'E':
+                    isExponential = true;
+                    break;
+            }
+            if (foundSeparator) {
+                break;
+            }
+        }
+        // When there is nothing found, then we put the end position to the end
+        // of the string.
+        result.mEndPosition = currentIndex;
+    }
+
+    /**
+     * Parse the floats in the string. This is an optimized version of
+     * parseFloat(s.split(",|\\s"));
+     *
+     * @param s the string containing a command and list of floats
+     *
+     * @return array of floats
+     */
+    @NonNull
+    private static float[] getFloats(@NonNull String s) {
+        if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
+            return new float[0];
+        }
+        try {
+            float[] results = new float[s.length()];
+            int count = 0;
+            int startPosition = 1;
+            int endPosition;
+
+            ExtractFloatResult result = new ExtractFloatResult();
+            int totalLength = s.length();
+
+            // The startPosition should always be the first character of the
+            // current number, and endPosition is the character after the current
+            // number.
+            while (startPosition < totalLength) {
+                extract(s, startPosition, result);
+                endPosition = result.mEndPosition;
+
+                if (startPosition < endPosition) {
+                    results[count++] = Float.parseFloat(
+                            s.substring(startPosition, endPosition));
+                }
+
+                if (result.mEndWithNegOrDot) {
+                    // Keep the '-' or '.' sign with next number.
+                    startPosition = endPosition;
+                } else {
+                    startPosition = endPosition + 1;
+                }
+            }
+            return Arrays.copyOf(results, count);
+        } catch (NumberFormatException e) {
+            assert false : "error in parsing \"" + s + "\"" + e;
+            return new float[0];
+        }
+    }
+
+
+    private static void addNode(@NonNull ArrayList<PathDataNode> list, char cmd,
+            @NonNull float[] val) {
+        list.add(new PathDataNode(cmd, val));
+    }
+
+    private static class ExtractFloatResult {
+        // We need to return the position of the next separator and whether the
+        // next float starts with a '-' or a '.'.
+        private int mEndPosition;
+        private boolean mEndWithNegOrDot;
+    }
+
+    /**
+     * Each PathDataNode represents one command in the "d" attribute of the svg file. An array of
+     * PathDataNode can represent the whole "d" attribute.
+     */
+    public static class PathDataNode {
+        private char mType;
+        @NonNull
+        private float[] mParams;
+
+        private PathDataNode(char type, @NonNull float[] params) {
+            mType = type;
+            mParams = params;
+        }
+
+        public char getType() {
+            return mType;
+        }
+
+        @NonNull
+        public float[] getParams() {
+            return mParams;
+        }
+
+        private PathDataNode(@NonNull PathDataNode n) {
+            mType = n.mType;
+            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
+        }
+
+        /**
+         * Convert an array of PathDataNode to Path. Reset the passed path as needed before
+         * calling this method.
+         *
+         * @param node The source array of PathDataNode.
+         * @param path The target Path object.
+         */
+        public static void nodesToPath(@NonNull PathDataNode[] node, @NonNull Path_Delegate path) {
+            float[] current = new float[6];
+            char previousCommand = 'm';
+            //noinspection ForLoopReplaceableByForEach
+            for (int i = 0; i < node.length; i++) {
+                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
+                previousCommand = node[i].mType;
+            }
+        }
+
+        /**
+         * The current PathDataNode will be interpolated between the <code>nodeFrom</code> and
+         * <code>nodeTo</code> according to the <code>fraction</code>.
+         *
+         * @param nodeFrom The start value as a PathDataNode.
+         * @param nodeTo The end value as a PathDataNode
+         * @param fraction The fraction to interpolate.
+         */
+        private void interpolatePathDataNode(@NonNull PathDataNode nodeFrom,
+                @NonNull PathDataNode nodeTo, float fraction) {
+            for (int i = 0; i < nodeFrom.mParams.length; i++) {
+                mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
+                        + nodeTo.mParams[i] * fraction;
+            }
+        }
+
+        @SuppressWarnings("PointlessArithmeticExpression")
+        private static void addCommand(@NonNull Path_Delegate path, float[] current,
+                char previousCmd, char cmd, @NonNull float[] val) {
+
+            int incr = 2;
+            float currentX = current[0];
+            float currentY = current[1];
+            float ctrlPointX = current[2];
+            float ctrlPointY = current[3];
+            float currentSegmentStartX = current[4];
+            float currentSegmentStartY = current[5];
+            float reflectiveCtrlPointX;
+            float reflectiveCtrlPointY;
+
+            switch (cmd) {
+                case 'z':
+                case 'Z':
+                    path.close();
+                    // Path is closed here, but we need to move the pen to the
+                    // closed position. So we cache the segment's starting position,
+                    // and restore it here.
+                    currentX = currentSegmentStartX;
+                    currentY = currentSegmentStartY;
+                    ctrlPointX = currentSegmentStartX;
+                    ctrlPointY = currentSegmentStartY;
+                    path.moveTo(currentX, currentY);
+                    break;
+                case 'm':
+                case 'M':
+                case 'l':
+                case 'L':
+                case 't':
+                case 'T':
+                    incr = 2;
+                    break;
+                case 'h':
+                case 'H':
+                case 'v':
+                case 'V':
+                    incr = 1;
+                    break;
+                case 'c':
+                case 'C':
+                    incr = 6;
+                    break;
+                case 's':
+                case 'S':
+                case 'q':
+                case 'Q':
+                    incr = 4;
+                    break;
+                case 'a':
+                case 'A':
+                    incr = 7;
+                    break;
+            }
+
+            for (int k = 0; k < val.length; k += incr) {
+                switch (cmd) {
+                    case 'm': // moveto - Start a new sub-path (relative)
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+
+                        if (k > 0) {
+                            // According to the spec, if a moveto is followed by multiple
+                            // pairs of coordinates, the subsequent pairs are treated as
+                            // implicit lineto commands.
+                            path.rLineTo(val[k + 0], val[k + 1]);
+                        } else {
+                            path.rMoveTo(val[k + 0], val[k + 1]);
+                            currentSegmentStartX = currentX;
+                            currentSegmentStartY = currentY;
+                        }
+                        break;
+                    case 'M': // moveto - Start a new sub-path
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+
+                        if (k > 0) {
+                            // According to the spec, if a moveto is followed by multiple
+                            // pairs of coordinates, the subsequent pairs are treated as
+                            // implicit lineto commands.
+                            path.lineTo(val[k + 0], val[k + 1]);
+                        } else {
+                            path.moveTo(val[k + 0], val[k + 1]);
+                            currentSegmentStartX = currentX;
+                            currentSegmentStartY = currentY;
+                        }
+                        break;
+                    case 'l': // lineto - Draw a line from the current point (relative)
+                        path.rLineTo(val[k + 0], val[k + 1]);
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'L': // lineto - Draw a line from the current point
+                        path.lineTo(val[k + 0], val[k + 1]);
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
+                        path.rLineTo(val[k + 0], 0);
+                        currentX += val[k + 0];
+                        break;
+                    case 'H': // horizontal lineto - Draws a horizontal line
+                        path.lineTo(val[k + 0], currentY);
+                        currentX = val[k + 0];
+                        break;
+                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+                        path.rLineTo(0, val[k + 0]);
+                        currentY += val[k + 0];
+                        break;
+                    case 'V': // vertical lineto - Draws a vertical line from the current point
+                        path.lineTo(currentX, val[k + 0]);
+                        currentY = val[k + 0];
+                        break;
+                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
+                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+                                val[k + 4], val[k + 5]);
+
+                        ctrlPointX = currentX + val[k + 2];
+                        ctrlPointY = currentY + val[k + 3];
+                        currentX += val[k + 4];
+                        currentY += val[k + 5];
+
+                        break;
+                    case 'C': // curveto - Draws a cubic Bézier curve
+                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+                                val[k + 4], val[k + 5]);
+                        currentX = val[k + 4];
+                        currentY = val[k + 5];
+                        ctrlPointX = val[k + 2];
+                        ctrlPointY = val[k + 3];
+                        break;
+                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+                        reflectiveCtrlPointX = 0;
+                        reflectiveCtrlPointY = 0;
+                        if (previousCmd == 'c' || previousCmd == 's'
+                                || previousCmd == 'C' || previousCmd == 'S') {
+                            reflectiveCtrlPointX = currentX - ctrlPointX;
+                            reflectiveCtrlPointY = currentY - ctrlPointY;
+                        }
+                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1],
+                                val[k + 2], val[k + 3]);
+
+                        ctrlPointX = currentX + val[k + 0];
+                        ctrlPointY = currentY + val[k + 1];
+                        currentX += val[k + 2];
+                        currentY += val[k + 3];
+                        break;
+                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+                        reflectiveCtrlPointX = currentX;
+                        reflectiveCtrlPointY = currentY;
+                        if (previousCmd == 'c' || previousCmd == 's'
+                                || previousCmd == 'C' || previousCmd == 'S') {
+                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+                        }
+                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = val[k + 0];
+                        ctrlPointY = val[k + 1];
+                        currentX = val[k + 2];
+                        currentY = val[k + 3];
+                        break;
+                    case 'q': // Draws a quadratic Bézier (relative)
+                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = currentX + val[k + 0];
+                        ctrlPointY = currentY + val[k + 1];
+                        currentX += val[k + 2];
+                        currentY += val[k + 3];
+                        break;
+                    case 'Q': // Draws a quadratic Bézier
+                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = val[k + 0];
+                        ctrlPointY = val[k + 1];
+                        currentX = val[k + 2];
+                        currentY = val[k + 3];
+                        break;
+                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+                        reflectiveCtrlPointX = 0;
+                        reflectiveCtrlPointY = 0;
+                        if (previousCmd == 'q' || previousCmd == 't'
+                                || previousCmd == 'Q' || previousCmd == 'T') {
+                            reflectiveCtrlPointX = currentX - ctrlPointX;
+                            reflectiveCtrlPointY = currentY - ctrlPointY;
+                        }
+                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1]);
+                        ctrlPointX = currentX + reflectiveCtrlPointX;
+                        ctrlPointY = currentY + reflectiveCtrlPointY;
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
+                        reflectiveCtrlPointX = currentX;
+                        reflectiveCtrlPointY = currentY;
+                        if (previousCmd == 'q' || previousCmd == 't'
+                                || previousCmd == 'Q' || previousCmd == 'T') {
+                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+                        }
+                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1]);
+                        ctrlPointX = reflectiveCtrlPointX;
+                        ctrlPointY = reflectiveCtrlPointY;
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'a': // Draws an elliptical arc
+                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+                        drawArc(path,
+                                currentX,
+                                currentY,
+                                val[k + 5] + currentX,
+                                val[k + 6] + currentY,
+                                val[k + 0],
+                                val[k + 1],
+                                val[k + 2],
+                                val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        currentX += val[k + 5];
+                        currentY += val[k + 6];
+                        ctrlPointX = currentX;
+                        ctrlPointY = currentY;
+                        break;
+                    case 'A': // Draws an elliptical arc
+                        drawArc(path,
+                                currentX,
+                                currentY,
+                                val[k + 5],
+                                val[k + 6],
+                                val[k + 0],
+                                val[k + 1],
+                                val[k + 2],
+                                val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        currentX = val[k + 5];
+                        currentY = val[k + 6];
+                        ctrlPointX = currentX;
+                        ctrlPointY = currentY;
+                        break;
+                }
+                previousCmd = cmd;
+            }
+            current[0] = currentX;
+            current[1] = currentY;
+            current[2] = ctrlPointX;
+            current[3] = ctrlPointY;
+            current[4] = currentSegmentStartX;
+            current[5] = currentSegmentStartY;
+        }
+
+        private static void drawArc(@NonNull Path_Delegate p, float x0, float y0, float x1,
+                float y1, float a, float b, float theta, boolean isMoreThanHalf,
+                boolean isPositiveArc) {
+
+            LOGGER.log(Level.FINE, "(" + x0 + "," + y0 + ")-(" + x1 + "," + y1
+                    + ") {" + a + " " + b + "}");
+        /* Convert rotation angle from degrees to radians */
+            double thetaD = theta * Math.PI / 180.0f;
+        /* Pre-compute rotation matrix entries */
+            double cosTheta = Math.cos(thetaD);
+            double sinTheta = Math.sin(thetaD);
+        /* Transform (x0, y0) and (x1, y1) into unit space */
+        /* using (inverse) rotation, followed by (inverse) scale */
+            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+            LOGGER.log(Level.FINE, "unit space (" + x0p + "," + y0p + ")-(" + x1p
+                    + "," + y1p + ")");
+        /* Compute differences and averages */
+            double dx = x0p - x1p;
+            double dy = y0p - y1p;
+            double xm = (x0p + x1p) / 2;
+            double ym = (y0p + y1p) / 2;
+        /* Solve for intersecting unit circles */
+            double dsq = dx * dx + dy * dy;
+            if (dsq == 0.0) {
+                LOGGER.log(Level.FINE, " Points are coincident");
+                return; /* Points are coincident */
+            }
+            double disc = 1.0 / dsq - 1.0 / 4.0;
+            if (disc < 0.0) {
+                LOGGER.log(Level.FINE, "Points are too far apart " + dsq);
+                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+                drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta,
+                        isMoreThanHalf, isPositiveArc);
+                return; /* Points are too far apart */
+            }
+            double s = Math.sqrt(disc);
+            double sdx = s * dx;
+            double sdy = s * dy;
+            double cx;
+            double cy;
+            if (isMoreThanHalf == isPositiveArc) {
+                cx = xm - sdy;
+                cy = ym + sdx;
+            } else {
+                cx = xm + sdy;
+                cy = ym - sdx;
+            }
+
+            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+            LOGGER.log(Level.FINE, "eta0 = Math.atan2( " + (y0p - cy) + " , "
+                    + (x0p - cx) + ") = " + Math.toDegrees(eta0));
+
+            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+            LOGGER.log(Level.FINE, "eta1 = Math.atan2( " + (y1p - cy) + " , "
+                    + (x1p - cx) + ") = " + Math.toDegrees(eta1));
+            double sweep = (eta1 - eta0);
+            if (isPositiveArc != (sweep >= 0)) {
+                if (sweep > 0) {
+                    sweep -= 2 * Math.PI;
+                } else {
+                    sweep += 2 * Math.PI;
+                }
+            }
+
+            cx *= a;
+            cy *= b;
+            double tcx = cx;
+            cx = cx * cosTheta - cy * sinTheta;
+            cy = tcx * sinTheta + cy * cosTheta;
+            LOGGER.log(
+                    Level.FINE,
+                    "cx, cy, a, b, x0, y0, thetaD, eta0, sweep = " + cx + " , "
+                            + cy + " , " + a + " , " + b + " , " + x0 + " , " + y0
+                            + " , " + Math.toDegrees(thetaD) + " , "
+                            + Math.toDegrees(eta0) + " , " + Math.toDegrees(sweep));
+
+            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+        }
+
+        /**
+         * Converts an arc to cubic Bezier segments and records them in p.
+         *
+         * @param p The target for the cubic Bezier segments
+         * @param cx The x coordinate center of the ellipse
+         * @param cy The y coordinate center of the ellipse
+         * @param a The radius of the ellipse in the horizontal direction
+         * @param b The radius of the ellipse in the vertical direction
+         * @param e1x E(eta1) x coordinate of the starting point of the arc
+         * @param e1y E(eta2) y coordinate of the starting point of the arc
+         * @param theta The angle that the ellipse bounding rectangle makes with the horizontal
+         * plane
+         * @param start The start angle of the arc on the ellipse
+         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+         */
+        private static void arcToBezier(@NonNull Path_Delegate p, double cx, double cy, double a,
+                double b, double e1x, double e1y, double theta, double start,
+                double sweep) {
+            // Taken from equations at:
+            // http://spaceroots.org/documents/ellipse/node8.html
+            // and http://www.spaceroots.org/documents/ellipse/node22.html
+            // Maximum of 45 degrees per cubic Bezier segment
+            int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+
+
+            double eta1 = start;
+            double cosTheta = Math.cos(theta);
+            double sinTheta = Math.sin(theta);
+            double cosEta1 = Math.cos(eta1);
+            double sinEta1 = Math.sin(eta1);
+            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+            double anglePerSegment = sweep / numSegments;
+            for (int i = 0; i < numSegments; i++) {
+                double eta2 = eta1 + anglePerSegment;
+                double sinEta2 = Math.sin(eta2);
+                double cosEta2 = Math.cos(eta2);
+                double e2x = cx + (a * cosTheta * cosEta2)
+                        - (b * sinTheta * sinEta2);
+                double e2y = cy + (a * sinTheta * cosEta2)
+                        + (b * cosTheta * sinEta2);
+                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+                double alpha = Math.sin(eta2 - eta1)
+                        * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+                double q1x = e1x + alpha * ep1x;
+                double q1y = e1y + alpha * ep1y;
+                double q2x = e2x - alpha * ep2x;
+                double q2y = e2y - alpha * ep2y;
+
+                p.cubicTo((float) q1x,
+                        (float) q1y,
+                        (float) q2x,
+                        (float) q2y,
+                        (float) e2x,
+                        (float) e2y);
+                eta1 = eta2;
+                e1x = e2x;
+                e1y = e2y;
+                ep1x = ep2x;
+                ep1y = ep2y;
+            }
+        }
+    }
+}
diff --git a/android/util/Patterns.java b/android/util/Patterns.java
new file mode 100644
index 0000000..50cd7b1
--- /dev/null
+++ b/android/util/Patterns.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Commonly used regular expression patterns.
+ */
+public class Patterns {
+    /**
+     *  Regular expression to match all IANA top-level domains.
+     *  List accurate as of 2011/07/18.  List taken from:
+     *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+     *  This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
+     *
+     *  @deprecated Due to the recent profileration of gTLDs, this API is
+     *  expected to become out-of-date very quickly. Therefore it is now
+     *  deprecated.
+     */
+    @Deprecated
+    public static final String TOP_LEVEL_DOMAIN_STR =
+        "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+        + "|(biz|b[abdefghijmnorstvwyz])"
+        + "|(cat|com|coop|c[acdfghiklmnoruvxyz])"
+        + "|d[ejkmoz]"
+        + "|(edu|e[cegrstu])"
+        + "|f[ijkmor]"
+        + "|(gov|g[abdefghilmnpqrstuwy])"
+        + "|h[kmnrtu]"
+        + "|(info|int|i[delmnoqrst])"
+        + "|(jobs|j[emop])"
+        + "|k[eghimnprwyz]"
+        + "|l[abcikrstuvy]"
+        + "|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
+        + "|(name|net|n[acefgilopruz])"
+        + "|(org|om)"
+        + "|(pro|p[aefghklmnrstwy])"
+        + "|qa"
+        + "|r[eosuw]"
+        + "|s[abcdeghijklmnortuvyz]"
+        + "|(tel|travel|t[cdfghjklmnoprtvwz])"
+        + "|u[agksyz]"
+        + "|v[aceginu]"
+        + "|w[fs]"
+        + "|(\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)"
+        + "|y[et]"
+        + "|z[amw])";
+
+    /**
+     *  Regular expression pattern to match all IANA top-level domains.
+     *  @deprecated This API is deprecated. See {@link #TOP_LEVEL_DOMAIN_STR}.
+     */
+    @Deprecated
+    public static final Pattern TOP_LEVEL_DOMAIN =
+        Pattern.compile(TOP_LEVEL_DOMAIN_STR);
+
+    /**
+     *  Regular expression to match all IANA top-level domains for WEB_URL.
+     *  List accurate as of 2011/07/18.  List taken from:
+     *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+     *  This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
+     *
+     *  @deprecated This API is deprecated. See {@link #TOP_LEVEL_DOMAIN_STR}.
+     */
+    @Deprecated
+    public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
+        "(?:"
+        + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+        + "|(?:biz|b[abdefghijmnorstvwyz])"
+        + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
+        + "|d[ejkmoz]"
+        + "|(?:edu|e[cegrstu])"
+        + "|f[ijkmor]"
+        + "|(?:gov|g[abdefghilmnpqrstuwy])"
+        + "|h[kmnrtu]"
+        + "|(?:info|int|i[delmnoqrst])"
+        + "|(?:jobs|j[emop])"
+        + "|k[eghimnprwyz]"
+        + "|l[abcikrstuvy]"
+        + "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
+        + "|(?:name|net|n[acefgilopruz])"
+        + "|(?:org|om)"
+        + "|(?:pro|p[aefghklmnrstwy])"
+        + "|qa"
+        + "|r[eosuw]"
+        + "|s[abcdeghijklmnortuvyz]"
+        + "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
+        + "|u[agksyz]"
+        + "|v[aceginu]"
+        + "|w[fs]"
+        + "|(?:\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)"
+        + "|y[et]"
+        + "|z[amw]))";
+
+    /**
+     *  Regular expression to match all IANA top-level domains.
+     *
+     *  List accurate as of 2015/11/24.  List taken from:
+     *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+     *  This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
+     *
+     *  @hide
+     */
+    static final String IANA_TOP_LEVEL_DOMAINS =
+        "(?:"
+        + "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active"
+        + "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica|amsterdam"
+        + "|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia|associates"
+        + "|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])"
+        + "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva"
+        + "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black"
+        + "|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots|boutique"
+        + "|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build|builders|business"
+        + "|buzz|bzh|b[abdefghijmnorstvwyz])"
+        + "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards"
+        + "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center|ceo"
+        + "|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani|cisco"
+        + "|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed|coach"
+        + "|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec|condos"
+        + "|construction|consulting|contractors|cooking|cool|coop|corsica|country|coupons|courses"
+        + "|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])"
+        + "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta"
+        + "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount"
+        + "|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])"
+        + "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises"
+        + "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert|exposed"
+        + "|express|e[cegrstu])"
+        + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|feedback|ferrero|film"
+        + "|final|finance|financial|firmdale|fish|fishing|fit|fitness|flights|florist|flowers|flsmidth"
+        + "|fly|foo|football|forex|forsale|forum|foundation|frl|frogans|fund|furniture|futbol|fyi"
+        + "|f[ijkmor])"
+        + "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving"
+        + "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov|grainger"
+        + "|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru|g[abdefghilmnpqrstuwy])"
+        + "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey|holdings"
+        + "|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hsbc|hyundai"
+        + "|h[kmnrtu])"
+        + "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink|institute"
+        + "|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau|iwc|i[delmnoqrst])"
+        + "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])"
+        + "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto|k[eghimnprwyz])"
+        + "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease|leclerc"
+        + "|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde|link|live"
+        + "|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury|l[abcikrstuvy])"
+        + "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba"
+        + "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi|moda"
+        + "|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar"
+        + "|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])"
+        + "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk"
+        + "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])"
+        + "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka"
+        + "|otsuka|ovh|om)"
+        + "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography"
+        + "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation|plumbing"
+        + "|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties|property"
+        + "|protection|pub|p[aefghklmnrstwy])"
+        + "|(?:qpon|quebec|qa)"
+        + "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent|rentals"
+        + "|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip|rocher|rocks"
+        + "|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])"
+        + "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo"
+        + "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat|security"
+        + "|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles|site|ski"
+        + "|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space|spiegel|spreadbetting"
+        + "|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study|style|sucks|supplies"
+        + "|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems|s[abcdeghijklmnortuvxyz])"
+        + "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica"
+        + "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo|tools"
+        + "|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust|tui|t[cdfghjklmnortvwz])"
+        + "|(?:ubs|university|uno|uol|u[agksyz])"
+        + "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin"
+        + "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])"
+        + "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki|williamhill"
+        + "|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])"
+        + "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c|\u043c\u043a\u0434"
+        + "|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430|\u043e\u043d\u043b\u0430\u0439\u043d"
+        + "|\u043e\u0440\u0433|\u0440\u0443\u0441|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431"
+        + "|\u0443\u043a\u0440|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd|\u0627\u0631\u0627\u0645\u0643\u0648"
+        + "|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629"
+        + "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0627\u06cc\u0631\u0627\u0646"
+        + "|\u0628\u0627\u0632\u0627\u0631|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633"
+        + "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629|\u0634\u0628\u0643\u0629"
+        + "|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646|\u0641\u0644\u0633\u0637\u064a\u0646"
+        + "|\u0642\u0637\u0631|\u0643\u0648\u0645|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627"
+        + "|\u0645\u0648\u0642\u0639|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924"
+        + "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4"
+        + "|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd"
+        + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21|\u0e44\u0e17\u0e22"
+        + "|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb|\u30b3\u30e0|\u4e16\u754c"
+        + "|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51|\u4f01\u4e1a|\u4f5b\u5c71"
+        + "|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063"
+        + "|\u5546\u57ce|\u5546\u5e97|\u5546\u6807|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c"
+        + "|\u5e7f\u4e1c|\u6148\u5584|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c"
+        + "|\u65b0\u52a0\u5761|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f"
+        + "|\u70b9\u770b|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc"
+        + "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137"
+        + "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox"
+        + "|xerox|xin|xn\\-\\-11b4c3d|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g"
+        + "|xn\\-\\-3e0b707e|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim"
+        + "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks"
+        + "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais|xn\\-\\-9dbq2a"
+        + "|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd"
+        + "|xn\\-\\-czr694b|xn\\-\\-czrs0t|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h"
+        + "|xn\\-\\-estv75g|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s"
+        + "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c"
+        + "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i"
+        + "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d|xn\\-\\-kpry57d"
+        + "|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf|xn\\-\\-mgba3a3ejt"
+        + "|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e"
+        + "|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab"
+        + "|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema"
+        + "|xn\\-\\-nyqy26a|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh"
+        + "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g|xn\\-\\-s9brj9c"
+        + "|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y|xn\\-\\-vermgensberater\\-ctb"
+        + "|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a"
+        + "|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o"
+        + "|xn\\-\\-ygbi2ammx|xn\\-\\-zfr164b|xperia|xxx|xyz)"
+        + "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])"
+        + "|(?:zara|zip|zone|zuerich|z[amw]))";
+
+    /**
+     * Kept for backward compatibility reasons.
+     *
+     * @deprecated Deprecated since it does not include all IRI characters defined in RFC 3987
+     */
+    @Deprecated
+    public static final String GOOD_IRI_CHAR =
+        "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+
+    private static final String IP_ADDRESS_STRING =
+            "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
+            + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
+            + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+            + "|[1-9][0-9]|[0-9]))";
+    public static final Pattern IP_ADDRESS = Pattern.compile(IP_ADDRESS_STRING);
+
+    /**
+     * Valid UCS characters defined in RFC 3987. Excludes space characters.
+     */
+    private static final String UCS_CHAR = "[" +
+            "\u00A0-\uD7FF" +
+            "\uF900-\uFDCF" +
+            "\uFDF0-\uFFEF" +
+            "\uD800\uDC00-\uD83F\uDFFD" +
+            "\uD840\uDC00-\uD87F\uDFFD" +
+            "\uD880\uDC00-\uD8BF\uDFFD" +
+            "\uD8C0\uDC00-\uD8FF\uDFFD" +
+            "\uD900\uDC00-\uD93F\uDFFD" +
+            "\uD940\uDC00-\uD97F\uDFFD" +
+            "\uD980\uDC00-\uD9BF\uDFFD" +
+            "\uD9C0\uDC00-\uD9FF\uDFFD" +
+            "\uDA00\uDC00-\uDA3F\uDFFD" +
+            "\uDA40\uDC00-\uDA7F\uDFFD" +
+            "\uDA80\uDC00-\uDABF\uDFFD" +
+            "\uDAC0\uDC00-\uDAFF\uDFFD" +
+            "\uDB00\uDC00-\uDB3F\uDFFD" +
+            "\uDB44\uDC00-\uDB7F\uDFFD" +
+            "&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]";
+
+    /**
+     * Valid characters for IRI label defined in RFC 3987.
+     */
+    private static final String LABEL_CHAR = "a-zA-Z0-9" + UCS_CHAR;
+
+    /**
+     * Valid characters for IRI TLD defined in RFC 3987.
+     */
+    private static final String TLD_CHAR = "a-zA-Z" + UCS_CHAR;
+
+    /**
+     * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets.
+     */
+    private static final String IRI_LABEL =
+            "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}";
+
+    /**
+     * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters.
+     */
+    private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w";
+
+    private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" +")";
+
+    private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD;
+
+    private static final String DOMAIN_NAME_STR = "(" + HOST_NAME + "|" + IP_ADDRESS_STRING + ")";
+    public static final Pattern DOMAIN_NAME = Pattern.compile(DOMAIN_NAME_STR);
+
+    private static final String PROTOCOL = "(?i:http|https|rtsp)://";
+
+    /* A word boundary or end of input.  This is to stop foo.sure from matching as foo.su */
+    private static final String WORD_BOUNDARY = "(?:\\b|$|^)";
+
+    private static final String USER_INFO = "(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+            + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+            + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@";
+
+    private static final String PORT_NUMBER = "\\:\\d{1,5}";
+
+    private static final String PATH_AND_QUERY = "[/\\?](?:(?:[" + LABEL_CHAR
+            + ";/\\?:@&=#~"  // plus optional query params
+            + "\\-\\.\\+!\\*'\\(\\),_\\$])|(?:%[a-fA-F0-9]{2}))*";
+
+    /**
+     *  Regular expression pattern to match most part of RFC 3987
+     *  Internationalized URLs, aka IRIs.
+     */
+    public static final Pattern WEB_URL = Pattern.compile("("
+            + "("
+            + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?"
+            + "(?:" + DOMAIN_NAME_STR + ")"
+            + "(?:" + PORT_NUMBER + ")?"
+            + ")"
+            + "(" + PATH_AND_QUERY + ")?"
+            + WORD_BOUNDARY
+            + ")");
+
+    /**
+     * Regular expression that matches known TLDs and punycode TLDs
+     */
+    private static final String STRICT_TLD = "(?:" +
+            IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")";
+
+    /**
+     * Regular expression that matches host names using {@link #STRICT_TLD}
+     */
+    private static final String STRICT_HOST_NAME = "(?:(?:" + IRI_LABEL + "\\.)+"
+            + STRICT_TLD + ")";
+
+    /**
+     * Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or
+     * {@link #IP_ADDRESS}
+     */
+    private static final String STRICT_DOMAIN_NAME = "(?:" + STRICT_HOST_NAME + "|"
+            + IP_ADDRESS_STRING + ")";
+
+    /**
+     * Regular expression that matches domain names without a TLD
+     */
+    private static final String RELAXED_DOMAIN_NAME =
+            "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS_STRING + ")";
+
+    /**
+     * Regular expression to match strings that do not start with a supported protocol. The TLDs
+     * are expected to be one of the known TLDs.
+     */
+    private static final String WEB_URL_WITHOUT_PROTOCOL = "("
+            + WORD_BOUNDARY
+            + "(?<!:\\/\\/)"
+            + "("
+            + "(?:" + STRICT_DOMAIN_NAME + ")"
+            + "(?:" + PORT_NUMBER + ")?"
+            + ")"
+            + "(?:" + PATH_AND_QUERY + ")?"
+            + WORD_BOUNDARY
+            + ")";
+
+    /**
+     * Regular expression to match strings that start with a supported protocol. Rules for domain
+     * names and TLDs are more relaxed. TLDs are optional.
+     */
+    private static final String WEB_URL_WITH_PROTOCOL = "("
+            + WORD_BOUNDARY
+            + "(?:"
+            + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")"
+            + "(?:" + RELAXED_DOMAIN_NAME + ")?"
+            + "(?:" + PORT_NUMBER + ")?"
+            + ")"
+            + "(?:" + PATH_AND_QUERY + ")?"
+            + WORD_BOUNDARY
+            + ")";
+
+    /**
+     * Regular expression pattern to match IRIs. If a string starts with http(s):// the expression
+     * tries to match the URL structure with a relaxed rule for TLDs. If the string does not start
+     * with http(s):// the TLDs are expected to be one of the known TLDs.
+     *
+     * @hide
+     */
+    public static final Pattern AUTOLINK_WEB_URL = Pattern.compile(
+            "(" + WEB_URL_WITH_PROTOCOL + "|" + WEB_URL_WITHOUT_PROTOCOL + ")");
+
+    /**
+     * Regular expression for valid email characters. Does not include some of the valid characters
+     * defined in RFC5321: #&~!^`{}/=$*?|
+     */
+    private static final String EMAIL_CHAR = LABEL_CHAR + "\\+\\-_%'";
+
+    /**
+     * Regular expression for local part of an email address. RFC5321 section 4.5.3.1.1 limits
+     * the local part to be at most 64 octets.
+     */
+    private static final String EMAIL_ADDRESS_LOCAL_PART =
+            "[" + EMAIL_CHAR + "]" + "(?:[" + EMAIL_CHAR + "\\.]{0,62}[" + EMAIL_CHAR + "])?";
+
+    /**
+     * Regular expression for the domain part of an email address. RFC5321 section 4.5.3.1.2 limits
+     * the domain to be at most 255 octets.
+     */
+    private static final String EMAIL_ADDRESS_DOMAIN =
+            "(?=.{1,255}(?:\\s|$|^))" + HOST_NAME;
+
+    /**
+     * Regular expression pattern to match email addresses. It excludes double quoted local parts
+     * and the special characters #&~!^`{}/=$*?| that are included in RFC5321.
+     * @hide
+     */
+    public static final Pattern AUTOLINK_EMAIL_ADDRESS = Pattern.compile("(" + WORD_BOUNDARY +
+            "(?:" + EMAIL_ADDRESS_LOCAL_PART + "@" + EMAIL_ADDRESS_DOMAIN + ")" +
+            WORD_BOUNDARY + ")"
+    );
+
+    public static final Pattern EMAIL_ADDRESS
+        = Pattern.compile(
+            "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
+            "\\@" +
+            "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
+            "(" +
+                "\\." +
+                "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
+            ")+"
+        );
+
+    /**
+     * This pattern is intended for searching for things that look like they
+     * might be phone numbers in arbitrary text, not for validating whether
+     * something is in fact a phone number.  It will miss many things that
+     * are legitimate phone numbers.
+     *
+     * <p> The pattern matches the following:
+     * <ul>
+     * <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
+     * may follow.
+     * <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
+     * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
+     * </ul>
+     */
+    public static final Pattern PHONE
+        = Pattern.compile(                      // sdd = space, dot, or dash
+                "(\\+[0-9]+[\\- \\.]*)?"        // +<digits><sdd>*
+                + "(\\([0-9]+\\)[\\- \\.]*)?"   // (<digits>)<sdd>*
+                + "([0-9][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
+
+    /**
+     *  Convenience method to take all of the non-null matching groups in a
+     *  regex Matcher and return them as a concatenated string.
+     *
+     *  @param matcher      The Matcher object from which grouped text will
+     *                      be extracted
+     *
+     *  @return             A String comprising all of the non-null matched
+     *                      groups concatenated together
+     */
+    public static final String concatGroups(Matcher matcher) {
+        StringBuilder b = new StringBuilder();
+        final int numGroups = matcher.groupCount();
+
+        for (int i = 1; i <= numGroups; i++) {
+            String s = matcher.group(i);
+
+            if (s != null) {
+                b.append(s);
+            }
+        }
+
+        return b.toString();
+    }
+
+    /**
+     * Convenience method to return only the digits and plus signs
+     * in the matching string.
+     *
+     * @param matcher      The Matcher object from which digits and plus will
+     *                     be extracted
+     *
+     * @return             A String comprising all of the digits and plus in
+     *                     the match
+     */
+    public static final String digitsAndPlusOnly(Matcher matcher) {
+        StringBuilder buffer = new StringBuilder();
+        String matchingRegion = matcher.group();
+
+        for (int i = 0, size = matchingRegion.length(); i < size; i++) {
+            char character = matchingRegion.charAt(i);
+
+            if (character == '+' || Character.isDigit(character)) {
+                buffer.append(character);
+            }
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Do not create this static utility class.
+     */
+    private Patterns() {}
+}
diff --git a/android/util/Pools.java b/android/util/Pools.java
new file mode 100644
index 0000000..7ae3244
--- /dev/null
+++ b/android/util/Pools.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2009 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ *     private static final SynchronizedPool<MyPooledClass> sPool =
+ *             new SynchronizedPool<MyPooledClass>(10);
+ *
+ *     public static MyPooledClass obtain() {
+ *         MyPooledClass instance = sPool.acquire();
+ *         return (instance != null) ? instance : new MyPooledClass();
+ *     }
+ *
+ *     public void recycle() {
+ *          // Clear state if needed.
+ *          sPool.release(this);
+ *     }
+ *
+ *     . . .
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+public final class Pools {
+
+    /**
+     * Interface for managing a pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static interface Pool<T> {
+
+        /**
+         * @return An instance from the pool if such, null otherwise.
+         */
+        @UnsupportedAppUsage
+        public T acquire();
+
+        /**
+         * Release an instance to the pool.
+         *
+         * @param instance The instance to release.
+         * @return Whether the instance was put in the pool.
+         *
+         * @throws IllegalStateException If the instance is already in the pool.
+         */
+        @UnsupportedAppUsage
+        public boolean release(T instance);
+    }
+
+    private Pools() {
+        /* do nothing - hiding constructor */
+    }
+
+    /**
+     * Simple (non-synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SimplePool<T> implements Pool<T> {
+        @UnsupportedAppUsage
+        private final Object[] mPool;
+
+        private int mPoolSize;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        @UnsupportedAppUsage
+        public SimplePool(int maxPoolSize) {
+            if (maxPoolSize <= 0) {
+                throw new IllegalArgumentException("The max pool size must be > 0");
+            }
+            mPool = new Object[maxPoolSize];
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        @UnsupportedAppUsage
+        public T acquire() {
+            if (mPoolSize > 0) {
+                final int lastPooledIndex = mPoolSize - 1;
+                T instance = (T) mPool[lastPooledIndex];
+                mPool[lastPooledIndex] = null;
+                mPoolSize--;
+                return instance;
+            }
+            return null;
+        }
+
+        @Override
+        @UnsupportedAppUsage
+        public boolean release(T instance) {
+            if (isInPool(instance)) {
+                throw new IllegalStateException("Already in the pool!");
+            }
+            if (mPoolSize < mPool.length) {
+                mPool[mPoolSize] = instance;
+                mPoolSize++;
+                return true;
+            }
+            return false;
+        }
+
+        private boolean isInPool(T instance) {
+            for (int i = 0; i < mPoolSize; i++) {
+                if (mPool[i] == instance) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Synchronized pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SynchronizedPool<T> extends SimplePool<T> {
+        private final Object mLock;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         * @param lock an optional custom object to synchronize on
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SynchronizedPool(int maxPoolSize, Object lock) {
+            super(maxPoolSize);
+            mLock = lock;
+        }
+
+        /** @see #SynchronizedPool(int, Object)  */
+        @UnsupportedAppUsage
+        public SynchronizedPool(int maxPoolSize) {
+            this(maxPoolSize, new Object());
+        }
+
+        @Override
+        @UnsupportedAppUsage
+        public T acquire() {
+            synchronized (mLock) {
+                return super.acquire();
+            }
+        }
+
+        @Override
+        @UnsupportedAppUsage
+        public boolean release(T element) {
+            synchronized (mLock) {
+                return super.release(element);
+            }
+        }
+    }
+}
diff --git a/android/util/PrefixPrinter.java b/android/util/PrefixPrinter.java
new file mode 100644
index 0000000..62f7da1
--- /dev/null
+++ b/android/util/PrefixPrinter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 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;
+
+/**
+ * PrefixPrinter is a Printer which prefixes all lines with a given
+ * prefix.
+ *
+ * @hide
+ */
+public class PrefixPrinter implements Printer {
+    private final Printer mPrinter;
+    private final String mPrefix;
+
+    /**
+     * Creates a new PrefixPrinter.
+     *
+     * <p>If prefix is null or empty, the provided printer is returned, rather
+     * than making a prefixing printer.
+     */
+    public static Printer create(Printer printer, String prefix) {
+        if (prefix == null || prefix.equals("")) {
+            return printer;
+        }
+        return new PrefixPrinter(printer, prefix);
+    }
+
+    private PrefixPrinter(Printer printer, String prefix) {
+        mPrinter = printer;
+        mPrefix = prefix;
+    }
+
+    public void println(String str) {
+        mPrinter.println(mPrefix + str);
+    }
+}
diff --git a/android/util/PrintStreamPrinter.java b/android/util/PrintStreamPrinter.java
new file mode 100644
index 0000000..1c11f15
--- /dev/null
+++ b/android/util/PrintStreamPrinter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2006 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;
+
+import java.io.PrintStream;
+
+/**
+ * Implementation of a {@link android.util.Printer} that sends its output
+ * to a {@link java.io.PrintStream}.
+ */
+public class PrintStreamPrinter implements Printer {
+    private final PrintStream mPS;
+    
+    /**
+     * Create a new Printer that sends to a PrintWriter object.
+     * 
+     * @param pw The PrintWriter where you would like output to go.
+     */
+    public PrintStreamPrinter(PrintStream pw) {
+        mPS = pw;
+    }
+    
+    public void println(String x) {
+        mPS.println(x);
+    }
+}
diff --git a/android/util/PrintWriterPrinter.java b/android/util/PrintWriterPrinter.java
new file mode 100644
index 0000000..82c4d03
--- /dev/null
+++ b/android/util/PrintWriterPrinter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2006 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;
+
+import java.io.PrintWriter;
+
+/**
+ * Implementation of a {@link android.util.Printer} that sends its output
+ * to a {@link java.io.PrintWriter}.
+ */
+public class PrintWriterPrinter implements Printer {
+    private final PrintWriter mPW;
+    
+    /**
+     * Create a new Printer that sends to a PrintWriter object.
+     * 
+     * @param pw The PrintWriter where you would like output to go.
+     */
+    public PrintWriterPrinter(PrintWriter pw) {
+        mPW = pw;
+    }
+    
+    public void println(String x) {
+        mPW.println(x);
+    }
+}
diff --git a/android/util/Printer.java b/android/util/Printer.java
new file mode 100644
index 0000000..595cf70
--- /dev/null
+++ b/android/util/Printer.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * Simple interface for printing text, allowing redirection to various
+ * targets.  Standard implementations are {@link android.util.LogPrinter},
+ * {@link android.util.StringBuilderPrinter}, and
+ * {@link android.util.PrintWriterPrinter}.
+ */
+public interface Printer {
+    /**
+     * Write a line of text to the output.  There is no need to terminate
+     * the given string with a newline.
+     */
+    void println(String x);
+}
diff --git a/android/util/Property.java b/android/util/Property.java
new file mode 100644
index 0000000..146db80
--- /dev/null
+++ b/android/util/Property.java
@@ -0,0 +1,106 @@
+/*
+ * 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 android.util;
+
+
+/**
+ * A property is an abstraction that can be used to represent a <emb>mutable</em> value that is held
+ * in a <em>host</em> object. The Property's {@link #set(Object, Object)} or {@link #get(Object)}
+ * methods can be implemented in terms of the private fields of the host object, or via "setter" and
+ * "getter" methods or by some other mechanism, as appropriate.
+ *
+ * @param <T> The class on which the property is declared.
+ * @param <V> The type that this property represents.
+ */
+public abstract class Property<T, V> {
+
+    private final String mName;
+    private final Class<V> mType;
+
+    /**
+     * This factory method creates and returns a Property given the <code>class</code> and
+     * <code>name</code> parameters, where the <code>"name"</code> parameter represents either:
+     * <ul>
+     *     <li>a public <code>getName()</code> method on the class which takes no arguments, plus an
+     *     optional public <code>setName()</code> method which takes a value of the same type
+     *     returned by <code>getName()</code>
+     *     <li>a public <code>isName()</code> method on the class which takes no arguments, plus an
+     *     optional public <code>setName()</code> method which takes a value of the same type
+     *     returned by <code>isName()</code>
+     *     <li>a public <code>name</code> field on the class
+     * </ul>
+     *
+     * <p>If either of the get/is method alternatives is found on the class, but an appropriate
+     * <code>setName()</code> method is not found, the <code>Property</code> will be
+     * {@link #isReadOnly() readOnly}. Calling the {@link #set(Object, Object)} method on such
+     * a property is allowed, but will have no effect.</p>
+     *
+     * <p>If neither the methods nor the field are found on the class a
+     * {@link NoSuchPropertyException} exception will be thrown.</p>
+     */
+    public static <T, V> Property<T, V> of(Class<T> hostType, Class<V> valueType, String name) {
+        return new ReflectiveProperty<T, V>(hostType, valueType, name);
+    }
+
+    /**
+     * A constructor that takes an identifying name and {@link #getType() type} for the property.
+     */
+    public Property(Class<V> type, String name) {
+        mName = name;
+        mType = type;
+    }
+
+    /**
+     * Returns true if the {@link #set(Object, Object)} method does not set the value on the target
+     * object (in which case the {@link #set(Object, Object) set()} method should throw a {@link
+     * NoSuchPropertyException} exception). This may happen if the Property wraps functionality that
+     * allows querying the underlying value but not setting it. For example, the {@link #of(Class,
+     * Class, String)} factory method may return a Property with name "foo" for an object that has
+     * only a <code>getFoo()</code> or <code>isFoo()</code> method, but no matching
+     * <code>setFoo()</code> method.
+     */
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    /**
+     * Sets the value on <code>object</code> which this property represents. If the method is unable
+     * to set the value on the target object it will throw an {@link UnsupportedOperationException}
+     * exception.
+     */
+    public void set(T object, V value) {
+        throw new UnsupportedOperationException("Property " + getName() +" is read-only");
+    }
+
+    /**
+     * Returns the current value that this property represents on the given <code>object</code>.
+     */
+    public abstract V get(T object);
+
+    /**
+     * Returns the name for this property.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the type for this property.
+     */
+    public Class<V> getType() {
+        return mType;
+    }
+}
diff --git a/android/util/Range.java b/android/util/Range.java
new file mode 100644
index 0000000..f31ddd9
--- /dev/null
+++ b/android/util/Range.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static com.android.internal.util.Preconditions.*;
+
+import android.hardware.camera2.utils.HashCodeHelpers;
+
+/**
+ * Immutable class for describing the range of two numeric values.
+ * <p>
+ * A range (or "interval") defines the inclusive boundaries around a contiguous span of
+ * values of some {@link Comparable} type; for example,
+ * "integers from 1 to 100 inclusive."
+ * </p>
+ * <p>
+ * All ranges are bounded, and the left side of the range is always {@code <=}
+ * the right side of the range.
+ * </p>
+ *
+ * <p>Although the implementation itself is immutable, there is no restriction that objects
+ * stored must also be immutable. If mutable objects are stored here, then the range
+ * effectively becomes mutable. </p>
+ */
+public final class Range<T extends Comparable<? super T>> {
+    /**
+     * Create a new immutable range.
+     *
+     * <p>
+     * The endpoints are {@code [lower, upper]}; that
+     * is the range is bounded. {@code lower} must be {@link Comparable#compareTo lesser or equal}
+     * to {@code upper}.
+     * </p>
+     *
+     * @param lower The lower endpoint (inclusive)
+     * @param upper The upper endpoint (inclusive)
+     *
+     * @throws NullPointerException if {@code lower} or {@code upper} is {@code null}
+     */
+    public Range(final T lower, final T upper) {
+        mLower = checkNotNull(lower, "lower must not be null");
+        mUpper = checkNotNull(upper, "upper must not be null");
+
+        if (lower.compareTo(upper) > 0) {
+            throw new IllegalArgumentException("lower must be less than or equal to upper");
+        }
+    }
+
+    /**
+     * Create a new immutable range, with the argument types inferred.
+     *
+     * <p>
+     * The endpoints are {@code [lower, upper]}; that
+     * is the range is bounded. {@code lower} must be {@link Comparable#compareTo lesser or equal}
+     * to {@code upper}.
+     * </p>
+     *
+     * @param lower The lower endpoint (inclusive)
+     * @param upper The upper endpoint (inclusive)
+     *
+     * @throws NullPointerException if {@code lower} or {@code upper} is {@code null}
+     */
+    public static <T extends Comparable<? super T>> Range<T> create(final T lower, final T upper) {
+        return new Range<T>(lower, upper);
+    }
+
+    /**
+     * Get the lower endpoint.
+     *
+     * @return a non-{@code null} {@code T} reference
+     */
+    public T getLower() {
+        return mLower;
+    }
+
+    /**
+     * Get the upper endpoint.
+     *
+     * @return a non-{@code null} {@code T} reference
+     */
+    public T getUpper() {
+        return mUpper;
+    }
+
+    /**
+     * Checks if the {@code value} is within the bounds of this range.
+     *
+     * <p>A value is considered to be within this range if it's {@code >=}
+     * the lower endpoint <i>and</i> {@code <=} the upper endpoint (using the {@link Comparable}
+     * interface.)</p>
+     *
+     * @param value a non-{@code null} {@code T} reference
+     * @return {@code true} if the value is within this inclusive range, {@code false} otherwise
+     *
+     * @throws NullPointerException if {@code value} was {@code null}
+     */
+    public boolean contains(T value) {
+        checkNotNull(value, "value must not be null");
+
+        boolean gteLower = value.compareTo(mLower) >= 0;
+        boolean lteUpper  = value.compareTo(mUpper) <= 0;
+
+        return gteLower && lteUpper;
+    }
+
+    /**
+     * Checks if another {@code range} is within the bounds of this range.
+     *
+     * <p>A range is considered to be within this range if both of its endpoints
+     * are within this range.</p>
+     *
+     * @param range a non-{@code null} {@code T} reference
+     * @return {@code true} if the range is within this inclusive range, {@code false} otherwise
+     *
+     * @throws NullPointerException if {@code range} was {@code null}
+     */
+    public boolean contains(Range<T> range) {
+        checkNotNull(range, "value must not be null");
+
+        boolean gteLower = range.mLower.compareTo(mLower) >= 0;
+        boolean lteUpper = range.mUpper.compareTo(mUpper) <= 0;
+
+        return gteLower && lteUpper;
+    }
+
+    /**
+     * Compare two ranges for equality.
+     *
+     * <p>A range is considered equal if and only if both the lower and upper endpoints
+     * are also equal.</p>
+     *
+     * @return {@code true} if the ranges are equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        } else if (this == obj) {
+            return true;
+        } else if (obj instanceof Range) {
+            @SuppressWarnings("rawtypes")
+            Range other = (Range) obj;
+            return mLower.equals(other.mLower) && mUpper.equals(other.mUpper);
+        }
+        return false;
+    }
+
+    /**
+     * Clamps {@code value} to this range.
+     *
+     * <p>If the value is within this range, it is returned.  Otherwise, if it
+     * is {@code <} than the lower endpoint, the lower endpoint is returned,
+     * else the upper endpoint is returned. Comparisons are performed using the
+     * {@link Comparable} interface.</p>
+     *
+     * @param value a non-{@code null} {@code T} reference
+     * @return {@code value} clamped to this range.
+     */
+    public T clamp(T value) {
+        checkNotNull(value, "value must not be null");
+
+        if (value.compareTo(mLower) < 0) {
+            return mLower;
+        } else if (value.compareTo(mUpper) > 0) {
+            return mUpper;
+        } else {
+            return value;
+        }
+    }
+
+    /**
+     * Returns the intersection of this range and another {@code range}.
+     * <p>
+     * E.g. if a {@code <} b {@code <} c {@code <} d, the
+     * intersection of [a, c] and [b, d] ranges is [b, c].
+     * As the endpoints are object references, there is no guarantee
+     * which specific endpoint reference is used from the input ranges:</p>
+     * <p>
+     * E.g. if a {@code ==} a' {@code <} b {@code <} c, the
+     * intersection of [a, b] and [a', c] ranges could be either
+     * [a, b] or ['a, b], where [a, b] could be either the exact
+     * input range, or a newly created range with the same endpoints.</p>
+     *
+     * @param range a non-{@code null} {@code Range<T>} reference
+     * @return the intersection of this range and the other range.
+     *
+     * @throws NullPointerException if {@code range} was {@code null}
+     * @throws IllegalArgumentException if the ranges are disjoint.
+     */
+    public Range<T> intersect(Range<T> range) {
+        checkNotNull(range, "range must not be null");
+
+        int cmpLower = range.mLower.compareTo(mLower);
+        int cmpUpper = range.mUpper.compareTo(mUpper);
+
+        if (cmpLower <= 0 && cmpUpper >= 0) {
+            // range includes this
+            return this;
+        } else if (cmpLower >= 0 && cmpUpper <= 0) {
+            // this inludes range
+            return range;
+        } else {
+            return Range.create(
+                    cmpLower <= 0 ? mLower : range.mLower,
+                    cmpUpper >= 0 ? mUpper : range.mUpper);
+        }
+    }
+
+    /**
+     * Returns the intersection of this range and the inclusive range
+     * specified by {@code [lower, upper]}.
+     * <p>
+     * See {@link #intersect(Range)} for more details.</p>
+     *
+     * @param lower a non-{@code null} {@code T} reference
+     * @param upper a non-{@code null} {@code T} reference
+     * @return the intersection of this range and the other range
+     *
+     * @throws NullPointerException if {@code lower} or {@code upper} was {@code null}
+     * @throws IllegalArgumentException if the ranges are disjoint.
+     */
+    public Range<T> intersect(T lower, T upper) {
+        checkNotNull(lower, "lower must not be null");
+        checkNotNull(upper, "upper must not be null");
+
+        int cmpLower = lower.compareTo(mLower);
+        int cmpUpper = upper.compareTo(mUpper);
+
+        if (cmpLower <= 0 && cmpUpper >= 0) {
+            // [lower, upper] includes this
+            return this;
+        } else {
+            return Range.create(
+                    cmpLower <= 0 ? mLower : lower,
+                    cmpUpper >= 0 ? mUpper : upper);
+        }
+    }
+
+    /**
+     * Returns the smallest range that includes this range and
+     * another {@code range}.
+     * <p>
+     * E.g. if a {@code <} b {@code <} c {@code <} d, the
+     * extension of [a, c] and [b, d] ranges is [a, d].
+     * As the endpoints are object references, there is no guarantee
+     * which specific endpoint reference is used from the input ranges:</p>
+     * <p>
+     * E.g. if a {@code ==} a' {@code <} b {@code <} c, the
+     * extension of [a, b] and [a', c] ranges could be either
+     * [a, c] or ['a, c], where ['a, c] could be either the exact
+     * input range, or a newly created range with the same endpoints.</p>
+     *
+     * @param range a non-{@code null} {@code Range<T>} reference
+     * @return the extension of this range and the other range.
+     *
+     * @throws NullPointerException if {@code range} was {@code null}
+     */
+    public Range<T> extend(Range<T> range) {
+        checkNotNull(range, "range must not be null");
+
+        int cmpLower = range.mLower.compareTo(mLower);
+        int cmpUpper = range.mUpper.compareTo(mUpper);
+
+        if (cmpLower <= 0 && cmpUpper >= 0) {
+            // other includes this
+            return range;
+        } else if (cmpLower >= 0 && cmpUpper <= 0) {
+            // this inludes other
+            return this;
+        } else {
+            return Range.create(
+                    cmpLower >= 0 ? mLower : range.mLower,
+                    cmpUpper <= 0 ? mUpper : range.mUpper);
+        }
+    }
+
+    /**
+     * Returns the smallest range that includes this range and
+     * the inclusive range specified by {@code [lower, upper]}.
+     * <p>
+     * See {@link #extend(Range)} for more details.</p>
+     *
+     * @param lower a non-{@code null} {@code T} reference
+     * @param upper a non-{@code null} {@code T} reference
+     * @return the extension of this range and the other range.
+     *
+     * @throws NullPointerException if {@code lower} or {@code
+     *                              upper} was {@code null}
+     */
+    public Range<T> extend(T lower, T upper) {
+        checkNotNull(lower, "lower must not be null");
+        checkNotNull(upper, "upper must not be null");
+
+        int cmpLower = lower.compareTo(mLower);
+        int cmpUpper = upper.compareTo(mUpper);
+
+        if (cmpLower >= 0 && cmpUpper <= 0) {
+            // this inludes other
+            return this;
+        } else {
+            return Range.create(
+                    cmpLower >= 0 ? mLower : lower,
+                    cmpUpper <= 0 ? mUpper : upper);
+        }
+    }
+
+    /**
+     * Returns the smallest range that includes this range and
+     * the {@code value}.
+     * <p>
+     * See {@link #extend(Range)} for more details, as this method is
+     * equivalent to {@code extend(Range.create(value, value))}.</p>
+     *
+     * @param value a non-{@code null} {@code T} reference
+     * @return the extension of this range and the value.
+     *
+     * @throws NullPointerException if {@code value} was {@code null}
+     */
+    public Range<T> extend(T value) {
+        checkNotNull(value, "value must not be null");
+        return extend(value, value);
+    }
+
+    /**
+     * Return the range as a string representation {@code "[lower, upper]"}.
+     *
+     * @return string representation of the range
+     */
+    @Override
+    public String toString() {
+        return String.format("[%s, %s]", mLower, mUpper);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return HashCodeHelpers.hashCodeGeneric(mLower, mUpper);
+    }
+
+    private final T mLower;
+    private final T mUpper;
+};
diff --git a/android/util/Rational.java b/android/util/Rational.java
new file mode 100644
index 0000000..5930000
--- /dev/null
+++ b/android/util/Rational.java
@@ -0,0 +1,606 @@
+/*
+ * 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;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+
+/**
+ * <p>An immutable data type representation a rational number.</p>
+ *
+ * <p>Contains a pair of {@code int}s representing the numerator and denominator of a
+ * Rational number. </p>
+ */
+public final class Rational extends Number implements Comparable<Rational> {
+    /**
+     * Constant for the <em>Not-a-Number (NaN)</em> value of the {@code Rational} type.
+     *
+     * <p>A {@code NaN} value is considered to be equal to itself (that is {@code NaN.equals(NaN)}
+     * will return {@code true}; it is always greater than any non-{@code NaN} value (that is
+     * {@code NaN.compareTo(notNaN)} will return a number greater than {@code 0}).</p>
+     *
+     * <p>Equivalent to constructing a new rational with both the numerator and denominator
+     * equal to {@code 0}.</p>
+     */
+    public static final Rational NaN = new Rational(0, 0);
+
+    /**
+     * Constant for the positive infinity value of the {@code Rational} type.
+     *
+     * <p>Equivalent to constructing a new rational with a positive numerator and a denominator
+     * equal to {@code 0}.</p>
+     */
+    public static final Rational POSITIVE_INFINITY = new Rational(1, 0);
+
+    /**
+     * Constant for the negative infinity value of the {@code Rational} type.
+     *
+     * <p>Equivalent to constructing a new rational with a negative numerator and a denominator
+     * equal to {@code 0}.</p>
+     */
+    public static final Rational NEGATIVE_INFINITY = new Rational(-1, 0);
+
+    /**
+     * Constant for the zero value of the {@code Rational} type.
+     *
+     * <p>Equivalent to constructing a new rational with a numerator equal to {@code 0} and
+     * any non-zero denominator.</p>
+     */
+    public static final Rational ZERO = new Rational(0, 1);
+
+    /**
+     * Unique version number per class to be compliant with {@link java.io.Serializable}.
+     *
+     * <p>Increment each time the fields change in any way.</p>
+     */
+    private static final long serialVersionUID = 1L;
+
+    /*
+     * Do not change the order of these fields or add new instance fields to maintain the
+     * Serializable compatibility across API revisions.
+     */
+    @UnsupportedAppUsage
+    private final int mNumerator;
+    @UnsupportedAppUsage
+    private final int mDenominator;
+
+    /**
+     * <p>Create a {@code Rational} with a given numerator and denominator.</p>
+     *
+     * <p>The signs of the numerator and the denominator may be flipped such that the denominator
+     * is always positive. Both the numerator and denominator will be converted to their reduced
+     * forms (see {@link #equals} for more details).</p>
+     *
+     * <p>For example,
+     * <ul>
+     * <li>a rational of {@code 2/4} will be reduced to {@code 1/2}.
+     * <li>a rational of {@code 1/-1} will be flipped to {@code -1/1}
+     * <li>a rational of {@code 5/0} will be reduced to {@code 1/0}
+     * <li>a rational of {@code 0/5} will be reduced to {@code 0/1}
+     * </ul>
+     * </p>
+     *
+     * @param numerator the numerator of the rational
+     * @param denominator the denominator of the rational
+     *
+     * @see #equals
+     */
+    public Rational(int numerator, int denominator) {
+
+        if (denominator < 0) {
+            numerator = -numerator;
+            denominator = -denominator;
+        }
+
+        // Convert to reduced form
+        if (denominator == 0 && numerator > 0) {
+            mNumerator = 1; // +Inf
+            mDenominator = 0;
+        } else if (denominator == 0 && numerator < 0) {
+            mNumerator = -1; // -Inf
+            mDenominator = 0;
+        } else if (denominator == 0 && numerator == 0) {
+            mNumerator = 0; // NaN
+            mDenominator = 0;
+        } else if (numerator == 0) {
+            mNumerator = 0;
+            mDenominator = 1;
+        } else {
+            int gcd = gcd(numerator, denominator);
+
+            mNumerator = numerator / gcd;
+            mDenominator = denominator / gcd;
+        }
+    }
+
+    /**
+     * Gets the numerator of the rational.
+     *
+     * <p>The numerator will always return {@code 1} if this rational represents
+     * infinity (that is, the denominator is {@code 0}).</p>
+     */
+    public int getNumerator() {
+        return mNumerator;
+    }
+
+    /**
+     * Gets the denominator of the rational
+     *
+     * <p>The denominator may return {@code 0}, in which case the rational may represent
+     * positive infinity (if the numerator was positive), negative infinity (if the numerator
+     * was negative), or {@code NaN} (if the numerator was {@code 0}).</p>
+     *
+     * <p>The denominator will always return {@code 1} if the numerator is {@code 0}.
+     */
+    public int getDenominator() {
+        return mDenominator;
+    }
+
+    /**
+     * Indicates whether this rational is a <em>Not-a-Number (NaN)</em> value.
+     *
+     * <p>A {@code NaN} value occurs when both the numerator and the denominator are {@code 0}.</p>
+     *
+     * @return {@code true} if this rational is a <em>Not-a-Number (NaN)</em> value;
+     *         {@code false} if this is a (potentially infinite) number value
+     */
+    public boolean isNaN() {
+        return mDenominator == 0 && mNumerator == 0;
+    }
+
+    /**
+     * Indicates whether this rational represents an infinite value.
+     *
+     * <p>An infinite value occurs when the denominator is {@code 0} (but the numerator is not).</p>
+     *
+     * @return {@code true} if this rational is a (positive or negative) infinite value;
+     *         {@code false} if this is a finite number value (or {@code NaN})
+     */
+    public boolean isInfinite() {
+        return mNumerator != 0 && mDenominator == 0;
+    }
+
+    /**
+     * Indicates whether this rational represents a finite value.
+     *
+     * <p>A finite value occurs when the denominator is not {@code 0}; in other words
+     * the rational is neither infinity or {@code NaN}.</p>
+     *
+     * @return {@code true} if this rational is a (positive or negative) infinite value;
+     *         {@code false} if this is a finite number value (or {@code NaN})
+     */
+    public boolean isFinite() {
+        return mDenominator != 0;
+    }
+
+    /**
+     * Indicates whether this rational represents a zero value.
+     *
+     * <p>A zero value is a {@link #isFinite finite} rational with a numerator of {@code 0}.</p>
+     *
+     * @return {@code true} if this rational is finite zero value;
+     *         {@code false} otherwise
+     */
+    public boolean isZero() {
+        return isFinite() && mNumerator == 0;
+    }
+
+    private boolean isPosInf() {
+        return mDenominator == 0 && mNumerator > 0;
+    }
+
+    private boolean isNegInf() {
+        return mDenominator == 0 && mNumerator < 0;
+    }
+
+    /**
+     * <p>Compare this Rational to another object and see if they are equal.</p>
+     *
+     * <p>A Rational object can only be equal to another Rational object (comparing against any
+     * other type will return {@code false}).</p>
+     *
+     * <p>A Rational object is considered equal to another Rational object if and only if one of
+     * the following holds:</p>
+     * <ul><li>Both are {@code NaN}</li>
+     *     <li>Both are infinities of the same sign</li>
+     *     <li>Both have the same numerator and denominator in their reduced form</li>
+     * </ul>
+     *
+     * <p>A reduced form of a Rational is calculated by dividing both the numerator and the
+     * denominator by their greatest common divisor.</p>
+     *
+     * <pre>{@code
+     * (new Rational(1, 2)).equals(new Rational(1, 2)) == true   // trivially true
+     * (new Rational(2, 3)).equals(new Rational(1, 2)) == false  // trivially false
+     * (new Rational(1, 2)).equals(new Rational(2, 4)) == true   // true after reduction
+     * (new Rational(0, 0)).equals(new Rational(0, 0)) == true   // NaN.equals(NaN)
+     * (new Rational(1, 0)).equals(new Rational(5, 0)) == true   // both are +infinity
+     * (new Rational(1, 0)).equals(new Rational(-1, 0)) == false // +infinity != -infinity
+     * }</pre>
+     *
+     * @param obj a reference to another object
+     *
+     * @return A boolean that determines whether or not the two Rational objects are equal.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        return obj instanceof Rational && equals((Rational) obj);
+    }
+
+    private boolean equals(Rational other) {
+        return (mNumerator == other.mNumerator && mDenominator == other.mDenominator);
+    }
+
+    /**
+     * Return a string representation of this rational, e.g. {@code "1/2"}.
+     *
+     * <p>The following rules of conversion apply:
+     * <ul>
+     * <li>{@code NaN} values will return {@code "NaN"}
+     * <li>Positive infinity values will return {@code "Infinity"}
+     * <li>Negative infinity values will return {@code "-Infinity"}
+     * <li>All other values will return {@code "numerator/denominator"} where {@code numerator}
+     * and {@code denominator} are substituted with the appropriate numerator and denominator
+     * values.
+     * </ul></p>
+     */
+    @Override
+    public String toString() {
+        if (isNaN()) {
+            return "NaN";
+        } else if (isPosInf()) {
+            return "Infinity";
+        } else if (isNegInf()) {
+            return "-Infinity";
+        } else {
+            return mNumerator + "/" + mDenominator;
+        }
+    }
+
+    /**
+     * <p>Convert to a floating point representation.</p>
+     *
+     * @return The floating point representation of this rational number.
+     * @hide
+     */
+    public float toFloat() {
+        // TODO: remove this duplicate function (used in CTS and the shim)
+        return floatValue();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        // Bias the hash code for the first (2^16) values for both numerator and denominator
+        int numeratorFlipped = mNumerator << 16 | mNumerator >>> 16;
+
+        return mDenominator ^ numeratorFlipped;
+    }
+
+    /**
+     * Calculates the greatest common divisor using Euclid's algorithm.
+     *
+     * <p><em>Visible for testing only.</em></p>
+     *
+     * @param numerator the numerator in a fraction
+     * @param denominator the denominator in a fraction
+     *
+     * @return An int value representing the gcd. Always positive.
+     * @hide
+     */
+    public static int gcd(int numerator, int denominator) {
+        /*
+         * Non-recursive implementation of Euclid's algorithm:
+         *
+         *  gcd(a, 0) := a
+         *  gcd(a, b) := gcd(b, a mod b)
+         *
+         */
+        int a = numerator;
+        int b = denominator;
+
+        while (b != 0) {
+            int oldB = b;
+
+            b = a % b;
+            a = oldB;
+        }
+
+        return Math.abs(a);
+    }
+
+    /**
+     * Returns the value of the specified number as a {@code double}.
+     *
+     * <p>The {@code double} is calculated by converting both the numerator and denominator
+     * to a {@code double}; then returning the result of dividing the numerator by the
+     * denominator.</p>
+     *
+     * @return the divided value of the numerator and denominator as a {@code double}.
+     */
+    @Override
+    public double doubleValue() {
+        double num = mNumerator;
+        double den = mDenominator;
+
+        return num / den;
+    }
+
+    /**
+     * Returns the value of the specified number as a {@code float}.
+     *
+     * <p>The {@code float} is calculated by converting both the numerator and denominator
+     * to a {@code float}; then returning the result of dividing the numerator by the
+     * denominator.</p>
+     *
+     * @return the divided value of the numerator and denominator as a {@code float}.
+     */
+    @Override
+    public float floatValue() {
+        float num = mNumerator;
+        float den = mDenominator;
+
+        return num / den;
+    }
+
+    /**
+     * Returns the value of the specified number as a {@code int}.
+     *
+     * <p>{@link #isInfinite Finite} rationals are converted to an {@code int} value
+     * by dividing the numerator by the denominator; conversion for non-finite values happens
+     * identically to casting a floating point value to an {@code int}, in particular:
+     *
+     * <p>
+     * <ul>
+     * <li>Positive infinity saturates to the largest maximum integer
+     * {@link Integer#MAX_VALUE}</li>
+     * <li>Negative infinity saturates to the smallest maximum integer
+     * {@link Integer#MIN_VALUE}</li>
+     * <li><em>Not-A-Number (NaN)</em> returns {@code 0}.</li>
+     * </ul>
+     * </p>
+     *
+     * @return the divided value of the numerator and denominator as a {@code int}.
+     */
+    @Override
+    public int intValue() {
+        // Mimic float to int conversion rules from JLS 5.1.3
+
+        if (isPosInf()) {
+            return Integer.MAX_VALUE;
+        } else if (isNegInf()) {
+            return Integer.MIN_VALUE;
+        } else if (isNaN()) {
+            return 0;
+        } else { // finite
+            return mNumerator / mDenominator;
+        }
+    }
+
+    /**
+     * Returns the value of the specified number as a {@code long}.
+     *
+     * <p>{@link #isInfinite Finite} rationals are converted to an {@code long} value
+     * by dividing the numerator by the denominator; conversion for non-finite values happens
+     * identically to casting a floating point value to a {@code long}, in particular:
+     *
+     * <p>
+     * <ul>
+     * <li>Positive infinity saturates to the largest maximum long
+     * {@link Long#MAX_VALUE}</li>
+     * <li>Negative infinity saturates to the smallest maximum long
+     * {@link Long#MIN_VALUE}</li>
+     * <li><em>Not-A-Number (NaN)</em> returns {@code 0}.</li>
+     * </ul>
+     * </p>
+     *
+     * @return the divided value of the numerator and denominator as a {@code long}.
+     */
+    @Override
+    public long longValue() {
+        // Mimic float to long conversion rules from JLS 5.1.3
+
+        if (isPosInf()) {
+            return Long.MAX_VALUE;
+        } else if (isNegInf()) {
+            return Long.MIN_VALUE;
+        } else if (isNaN()) {
+            return 0;
+        } else { // finite
+            return mNumerator / mDenominator;
+        }
+    }
+
+    /**
+     * Returns the value of the specified number as a {@code short}.
+     *
+     * <p>{@link #isInfinite Finite} rationals are converted to a {@code short} value
+     * identically to {@link #intValue}; the {@code int} result is then truncated to a
+     * {@code short} before returning the value.</p>
+     *
+     * @return the divided value of the numerator and denominator as a {@code short}.
+     */
+    @Override
+    public short shortValue() {
+        return (short) intValue();
+    }
+
+    /**
+     * Compare this rational to the specified rational to determine their natural order.
+     *
+     * <p>{@link #NaN} is considered to be equal to itself and greater than all other
+     * {@code Rational} values. Otherwise, if the objects are not {@link #equals equal}, then
+     * the following rules apply:</p>
+     *
+     * <ul>
+     * <li>Positive infinity is greater than any other finite number (or negative infinity)
+     * <li>Negative infinity is less than any other finite number (or positive infinity)
+     * <li>The finite number represented by this rational is checked numerically
+     * against the other finite number by converting both rationals to a common denominator multiple
+     * and comparing their numerators.
+     * </ul>
+     *
+     * @param another the rational to be compared
+     *
+     * @return a negative integer, zero, or a positive integer as this object is less than,
+     *         equal to, or greater than the specified rational.
+     *
+     * @throws NullPointerException if {@code another} was {@code null}
+     */
+    @Override
+    public int compareTo(Rational another) {
+        checkNotNull(another, "another must not be null");
+
+        if (equals(another)) {
+            return 0;
+        } else if (isNaN()) { // NaN is greater than the other non-NaN value
+            return 1;
+        } else if (another.isNaN()) { // the other NaN is greater than this non-NaN value
+            return -1;
+        } else if (isPosInf() || another.isNegInf()) {
+            return 1; // positive infinity is greater than any non-NaN/non-posInf value
+        } else if (isNegInf() || another.isPosInf()) {
+            return -1; // negative infinity is less than any non-NaN/non-negInf value
+        }
+
+        // else both this and another are finite numbers
+
+        // make the denominators the same, then compare numerators
+        long thisNumerator = ((long)mNumerator) * another.mDenominator; // long to avoid overflow
+        long otherNumerator = ((long)another.mNumerator) * mDenominator; // long to avoid overflow
+
+        // avoid underflow from subtraction by doing comparisons
+        if (thisNumerator < otherNumerator) {
+            return -1;
+        } else if (thisNumerator > otherNumerator) {
+            return 1;
+        } else {
+            // This should be covered by #equals, but have this code path just in case
+            return 0;
+        }
+    }
+
+    /*
+     * Serializable implementation.
+     *
+     * The following methods are omitted:
+     * >> writeObject - the default is sufficient (field by field serialization)
+     * >> readObjectNoData - the default is sufficient (0s for both fields is a NaN)
+     */
+
+    /**
+     * writeObject with default serialized form - guards against
+     * deserializing non-reduced forms of the rational.
+     *
+     * @throws InvalidObjectException if the invariants were violated
+     */
+    private void readObject(java.io.ObjectInputStream in)
+            throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+
+        /*
+         * Guard against trying to deserialize illegal values (in this case, ones
+         * that don't have a standard reduced form).
+         *
+         * - Non-finite values must be one of [0, 1], [0, 0], [0, 1], [0, -1]
+         * - Finite values must always have their greatest common divisor as 1
+         */
+
+        if (mNumerator == 0) { // either zero or NaN
+            if (mDenominator == 1 || mDenominator == 0) {
+                return;
+            }
+            throw new InvalidObjectException(
+                    "Rational must be deserialized from a reduced form for zero values");
+        } else if (mDenominator == 0) { // either positive or negative infinity
+            if (mNumerator == 1 || mNumerator == -1) {
+                return;
+            }
+            throw new InvalidObjectException(
+                    "Rational must be deserialized from a reduced form for infinity values");
+        } else { // finite value
+            if (gcd(mNumerator, mDenominator) > 1) {
+                throw new InvalidObjectException(
+                        "Rational must be deserialized from a reduced form for finite values");
+            }
+        }
+    }
+
+    private static NumberFormatException invalidRational(String s) {
+        throw new NumberFormatException("Invalid Rational: \"" + s + "\"");
+    }
+
+    /**
+     * Parses the specified string as a rational value.
+     * <p>The ASCII characters {@code \}{@code u003a} (':') and
+     * {@code \}{@code u002f} ('/') are recognized as separators between
+     * the numerator and denumerator.</p>
+     * <p>
+     * For any {@code Rational r}: {@code Rational.parseRational(r.toString()).equals(r)}.
+     * However, the method also handles rational numbers expressed in the
+     * following forms:</p>
+     * <p>
+     * "<i>num</i>{@code /}<i>den</i>" or
+     * "<i>num</i>{@code :}<i>den</i>" {@code => new Rational(num, den);},
+     * where <i>num</i> and <i>den</i> are string integers potentially
+     * containing a sign, such as "-10", "+7" or "5".</p>
+     *
+     * <pre>{@code
+     * Rational.parseRational("3:+6").equals(new Rational(1, 2)) == true
+     * Rational.parseRational("-3/-6").equals(new Rational(1, 2)) == true
+     * Rational.parseRational("4.56") => throws NumberFormatException
+     * }</pre>
+     *
+     * @param string the string representation of a rational value.
+     * @return the rational value represented by {@code string}.
+     *
+     * @throws NumberFormatException if {@code string} cannot be parsed
+     * as a rational value.
+     * @throws NullPointerException if {@code string} was {@code null}
+     */
+    public static Rational parseRational(String string)
+            throws NumberFormatException {
+        checkNotNull(string, "string must not be null");
+
+        if (string.equals("NaN")) {
+            return NaN;
+        } else if (string.equals("Infinity")) {
+            return POSITIVE_INFINITY;
+        } else if (string.equals("-Infinity")) {
+            return NEGATIVE_INFINITY;
+        }
+
+        int sep_ix = string.indexOf(':');
+        if (sep_ix < 0) {
+            sep_ix = string.indexOf('/');
+        }
+        if (sep_ix < 0) {
+            throw invalidRational(string);
+        }
+        try {
+            return new Rational(Integer.parseInt(string.substring(0, sep_ix)),
+                    Integer.parseInt(string.substring(sep_ix + 1)));
+        } catch (NumberFormatException e) {
+            throw invalidRational(string);
+        }
+    }
+}
diff --git a/android/util/RecurrenceRule.java b/android/util/RecurrenceRule.java
new file mode 100644
index 0000000..a570e5e
--- /dev/null
+++ b/android/util/RecurrenceRule.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.time.Clock;
+import java.time.LocalTime;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Iterator;
+import java.util.Objects;
+
+/**
+ * Description of an event that should recur over time at a specific interval
+ * between two anchor points in time.
+ *
+ * @hide
+ */
+public class RecurrenceRule implements Parcelable {
+    private static final String TAG = "RecurrenceRule";
+    private static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int VERSION_INIT = 0;
+
+    /** {@hide} */
+    @VisibleForTesting
+    public static Clock sClock = Clock.systemDefaultZone();
+
+    @UnsupportedAppUsage
+    public final ZonedDateTime start;
+    public final ZonedDateTime end;
+    public final Period period;
+
+    public RecurrenceRule(ZonedDateTime start, ZonedDateTime end, Period period) {
+        this.start = start;
+        this.end = end;
+        this.period = period;
+    }
+
+    @Deprecated
+    public static RecurrenceRule buildNever() {
+        return new RecurrenceRule(null, null, null);
+    }
+
+    @Deprecated
+    @UnsupportedAppUsage
+    public static RecurrenceRule buildRecurringMonthly(int dayOfMonth, ZoneId zone) {
+        // Assume we started last January, since it has all possible days
+        final ZonedDateTime now = ZonedDateTime.now(sClock).withZoneSameInstant(zone);
+        final ZonedDateTime start = ZonedDateTime.of(
+                now.toLocalDate().minusYears(1).withMonth(1).withDayOfMonth(dayOfMonth),
+                LocalTime.MIDNIGHT, zone);
+        return new RecurrenceRule(start, null, Period.ofMonths(1));
+    }
+
+    private RecurrenceRule(Parcel source) {
+        start = convertZonedDateTime(source.readString());
+        end = convertZonedDateTime(source.readString());
+        period = convertPeriod(source.readString());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(convertZonedDateTime(start));
+        dest.writeString(convertZonedDateTime(end));
+        dest.writeString(convertPeriod(period));
+    }
+
+    public RecurrenceRule(DataInputStream in) throws IOException {
+        final int version = in.readInt();
+        switch (version) {
+            case VERSION_INIT:
+                start = convertZonedDateTime(BackupUtils.readString(in));
+                end = convertZonedDateTime(BackupUtils.readString(in));
+                period = convertPeriod(BackupUtils.readString(in));
+                break;
+            default:
+                throw new ProtocolException("Unknown version " + version);
+        }
+    }
+
+    public void writeToStream(DataOutputStream out) throws IOException {
+        out.writeInt(VERSION_INIT);
+        BackupUtils.writeString(out, convertZonedDateTime(start));
+        BackupUtils.writeString(out, convertZonedDateTime(end));
+        BackupUtils.writeString(out, convertPeriod(period));
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder("RecurrenceRule{")
+                .append("start=").append(start)
+                .append(" end=").append(end)
+                .append(" period=").append(period)
+                .append("}").toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(start, end, period);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof RecurrenceRule) {
+            final RecurrenceRule other = (RecurrenceRule) obj;
+            return Objects.equals(start, other.start)
+                    && Objects.equals(end, other.end)
+                    && Objects.equals(period, other.period);
+        }
+        return false;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<RecurrenceRule> CREATOR = new Parcelable.Creator<RecurrenceRule>() {
+        @Override
+        public RecurrenceRule createFromParcel(Parcel source) {
+            return new RecurrenceRule(source);
+        }
+
+        @Override
+        public RecurrenceRule[] newArray(int size) {
+            return new RecurrenceRule[size];
+        }
+    };
+
+    public boolean isRecurring() {
+        return period != null;
+    }
+
+    @Deprecated
+    public boolean isMonthly() {
+        return start != null
+                && period != null
+                && period.getYears() == 0
+                && period.getMonths() == 1
+                && period.getDays() == 0;
+    }
+
+    public Iterator<Range<ZonedDateTime>> cycleIterator() {
+        if (period != null) {
+            return new RecurringIterator();
+        } else {
+            return new NonrecurringIterator();
+        }
+    }
+
+    private class NonrecurringIterator implements Iterator<Range<ZonedDateTime>> {
+        boolean hasNext;
+
+        public NonrecurringIterator() {
+            hasNext = (start != null) && (end != null);
+        }
+
+        @Override
+        public boolean hasNext() {
+            return hasNext;
+        }
+
+        @Override
+        public Range<ZonedDateTime> next() {
+            hasNext = false;
+            return new Range<>(start, end);
+        }
+    }
+
+    private class RecurringIterator implements Iterator<Range<ZonedDateTime>> {
+        int i;
+        ZonedDateTime cycleStart;
+        ZonedDateTime cycleEnd;
+
+        public RecurringIterator() {
+            final ZonedDateTime anchor = (end != null) ? end
+                    : ZonedDateTime.now(sClock).withZoneSameInstant(start.getZone());
+            if (LOGD) Log.d(TAG, "Resolving using anchor " + anchor);
+
+            updateCycle();
+
+            // Walk forwards until we find first cycle after now
+            while (anchor.toEpochSecond() > cycleEnd.toEpochSecond()) {
+                i++;
+                updateCycle();
+            }
+
+            // Walk backwards until we find first cycle before now
+            while (anchor.toEpochSecond() <= cycleStart.toEpochSecond()) {
+                i--;
+                updateCycle();
+            }
+        }
+
+        private void updateCycle() {
+            cycleStart = roundBoundaryTime(start.plus(period.multipliedBy(i)));
+            cycleEnd = roundBoundaryTime(start.plus(period.multipliedBy(i + 1)));
+        }
+
+        private ZonedDateTime roundBoundaryTime(ZonedDateTime boundary) {
+            if (isMonthly() && (boundary.getDayOfMonth() < start.getDayOfMonth())) {
+                // When forced to end a monthly cycle early, we want to count
+                // that entire day against the boundary.
+                return ZonedDateTime.of(boundary.toLocalDate(), LocalTime.MAX, start.getZone());
+            } else {
+                return boundary;
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            return cycleStart.toEpochSecond() >= start.toEpochSecond();
+        }
+
+        @Override
+        public Range<ZonedDateTime> next() {
+            if (LOGD) Log.d(TAG, "Cycle " + i + " from " + cycleStart + " to " + cycleEnd);
+            Range<ZonedDateTime> r = new Range<>(cycleStart, cycleEnd);
+            i--;
+            updateCycle();
+            return r;
+        }
+    }
+
+    public static String convertZonedDateTime(ZonedDateTime time) {
+        return time != null ? time.toString() : null;
+    }
+
+    public static ZonedDateTime convertZonedDateTime(String time) {
+        return time != null ? ZonedDateTime.parse(time) : null;
+    }
+
+    public static String convertPeriod(Period period) {
+        return period != null ? period.toString() : null;
+    }
+
+    public static Period convertPeriod(String period) {
+        return period != null ? Period.parse(period) : null;
+    }
+}
diff --git a/android/util/ReflectiveProperty.java b/android/util/ReflectiveProperty.java
new file mode 100644
index 0000000..6832240
--- /dev/null
+++ b/android/util/ReflectiveProperty.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 android.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Internal class to automatically generate a Property for a given class/name pair, given the
+ * specification of {@link Property#of(java.lang.Class, java.lang.Class, java.lang.String)}
+ */
+class ReflectiveProperty<T, V> extends Property<T, V> {
+
+    private static final String PREFIX_GET = "get";
+    private static final String PREFIX_IS = "is";
+    private static final String PREFIX_SET = "set";
+    private Method mSetter;
+    private Method mGetter;
+    private Field mField;
+
+    /**
+     * For given property name 'name', look for getName/isName method or 'name' field.
+     * Also look for setName method (optional - could be readonly). Failing method getters and
+     * field results in throwing NoSuchPropertyException.
+     *
+     * @param propertyHolder The class on which the methods or field are found
+     * @param name The name of the property, where this name is capitalized and appended to
+     * "get" and "is to search for the appropriate methods. If the get/is methods are not found,
+     * the constructor will search for a field with that exact name.
+     */
+    public ReflectiveProperty(Class<T> propertyHolder, Class<V> valueType, String name) {
+         // TODO: cache reflection info for each new class/name pair
+        super(valueType, name);
+        char firstLetter = Character.toUpperCase(name.charAt(0));
+        String theRest = name.substring(1);
+        String capitalizedName = firstLetter + theRest;
+        String getterName = PREFIX_GET + capitalizedName;
+        try {
+            mGetter = propertyHolder.getMethod(getterName, (Class<?>[])null);
+        } catch (NoSuchMethodException e) {
+            // getName() not available - try isName() instead
+            getterName = PREFIX_IS + capitalizedName;
+            try {
+                mGetter = propertyHolder.getMethod(getterName, (Class<?>[])null);
+            } catch (NoSuchMethodException e1) {
+                // Try public field instead
+                try {
+                    mField = propertyHolder.getField(name);
+                    Class fieldType = mField.getType();
+                    if (!typesMatch(valueType, fieldType)) {
+                        throw new NoSuchPropertyException("Underlying type (" + fieldType + ") " +
+                                "does not match Property type (" + valueType + ")");
+                    }
+                    return;
+                } catch (NoSuchFieldException e2) {
+                    // no way to access property - throw appropriate exception
+                    throw new NoSuchPropertyException("No accessor method or field found for"
+                            + " property with name " + name);
+                }
+            }
+        }
+        Class getterType = mGetter.getReturnType();
+        // Check to make sure our getter type matches our valueType
+        if (!typesMatch(valueType, getterType)) {
+            throw new NoSuchPropertyException("Underlying type (" + getterType + ") " +
+                    "does not match Property type (" + valueType + ")");
+        }
+        String setterName = PREFIX_SET + capitalizedName;
+        try {
+            mSetter = propertyHolder.getMethod(setterName, getterType);
+        } catch (NoSuchMethodException ignored) {
+            // Okay to not have a setter - just a readonly property
+        }
+    }
+
+    /**
+     * Utility method to check whether the type of the underlying field/method on the target
+     * object matches the type of the Property. The extra checks for primitive types are because
+     * generics will force the Property type to be a class, whereas the type of the underlying
+     * method/field will probably be a primitive type instead. Accept float as matching Float,
+     * etc.
+     */
+    private boolean typesMatch(Class<V> valueType, Class getterType) {
+        if (getterType != valueType) {
+            if (getterType.isPrimitive()) {
+                return (getterType == float.class && valueType == Float.class) ||
+                        (getterType == int.class && valueType == Integer.class) ||
+                        (getterType == boolean.class && valueType == Boolean.class) ||
+                        (getterType == long.class && valueType == Long.class) ||
+                        (getterType == double.class && valueType == Double.class) ||
+                        (getterType == short.class && valueType == Short.class) ||
+                        (getterType == byte.class && valueType == Byte.class) ||
+                        (getterType == char.class && valueType == Character.class);
+            }
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void set(T object, V value) {
+        if (mSetter != null) {
+            try {
+                mSetter.invoke(object, value);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        } else if (mField != null) {
+            try {
+                mField.set(object, value);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            }
+        } else {
+            throw new UnsupportedOperationException("Property " + getName() +" is read-only");
+        }
+    }
+
+    @Override
+    public V get(T object) {
+        if (mGetter != null) {
+            try {
+                return (V) mGetter.invoke(object, (Object[])null);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        } else if (mField != null) {
+            try {
+                return (V) mField.get(object);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            }
+        }
+        // Should not get here: there should always be a non-null getter or field
+        throw new AssertionError();
+    }
+
+    /**
+     * Returns false if there is no setter or public field underlying this Property.
+     */
+    @Override
+    public boolean isReadOnly() {
+        return (mSetter == null && mField == null);
+    }
+}
diff --git a/android/util/ResolvingAttributeSet.java b/android/util/ResolvingAttributeSet.java
new file mode 100644
index 0000000..6efd812
--- /dev/null
+++ b/android/util/ResolvingAttributeSet.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 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;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+public interface ResolvingAttributeSet extends AttributeSet {
+    /**
+     * Returns the resolved value of the attribute with the given name and namespace
+     *
+     * @param namespace the namespace of the attribute
+     * @param name the name of the attribute
+     * @return the resolved value of the attribute, or null if the attribute does not exist
+     */
+    @Nullable
+    ResourceValue getResolvedAttributeValue(@Nullable String namespace, @NonNull String name);
+}
diff --git a/android/util/RotationUtils.java b/android/util/RotationUtils.java
new file mode 100644
index 0000000..a44ed59
--- /dev/null
+++ b/android/util/RotationUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.graphics.Insets;
+import android.view.Surface.Rotation;
+
+/**
+ * A class containing utility methods related to rotation.
+ *
+ * @hide
+ */
+public class RotationUtils {
+
+    /**
+     * Rotates an Insets according to the given rotation.
+     */
+    public static Insets rotateInsets(Insets insets, @Rotation int rotation) {
+        if (insets == null || insets == Insets.NONE) {
+            return insets;
+        }
+        Insets rotated;
+        switch (rotation) {
+            case ROTATION_0:
+                rotated = insets;
+                break;
+            case ROTATION_90:
+                rotated = Insets.of(
+                        insets.top,
+                        insets.right,
+                        insets.bottom,
+                        insets.left);
+                break;
+            case ROTATION_180:
+                rotated = Insets.of(
+                        insets.right,
+                        insets.bottom,
+                        insets.left,
+                        insets.top);
+                break;
+            case ROTATION_270:
+                rotated = Insets.of(
+                        insets.bottom,
+                        insets.left,
+                        insets.top,
+                        insets.right);
+                break;
+            default:
+                throw new IllegalArgumentException("unknown rotation: " + rotation);
+        }
+        return rotated;
+    }
+}
diff --git a/android/util/Singleton.java b/android/util/Singleton.java
new file mode 100644
index 0000000..92646b4
--- /dev/null
+++ b/android/util/Singleton.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * Singleton helper class for lazily initialization.
+ *
+ * Modeled after frameworks/base/include/utils/Singleton.h
+ *
+ * @hide
+ */
+public abstract class Singleton<T> {
+
+    @UnsupportedAppUsage
+    public Singleton() {
+    }
+
+    @UnsupportedAppUsage
+    private T mInstance;
+
+    protected abstract T create();
+
+    @UnsupportedAppUsage
+    public final T get() {
+        synchronized (this) {
+            if (mInstance == null) {
+                mInstance = create();
+            }
+            return mInstance;
+        }
+    }
+}
diff --git a/android/util/Size.java b/android/util/Size.java
new file mode 100644
index 0000000..62df564
--- /dev/null
+++ b/android/util/Size.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+/**
+ * Immutable class for describing width and height dimensions in pixels.
+ */
+public final class Size {
+    /**
+     * Create a new immutable Size instance.
+     *
+     * @param width The width of the size, in pixels
+     * @param height The height of the size, in pixels
+     */
+    public Size(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    /**
+     * Get the width of the size (in pixels).
+     * @return width
+     */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Get the height of the size (in pixels).
+     * @return height
+     */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * Check if this size is equal to another size.
+     * <p>
+     * Two sizes are equal if and only if both their widths and heights are
+     * equal.
+     * </p>
+     * <p>
+     * A size object is never equal to any other type of object.
+     * </p>
+     *
+     * @return {@code true} if the objects were equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof Size) {
+            Size other = (Size) obj;
+            return mWidth == other.mWidth && mHeight == other.mHeight;
+        }
+        return false;
+    }
+
+    /**
+     * Return the size represented as a string with the format {@code "WxH"}
+     *
+     * @return string representation of the size
+     */
+    @Override
+    public String toString() {
+        return mWidth + "x" + mHeight;
+    }
+
+    private static NumberFormatException invalidSize(String s) {
+        throw new NumberFormatException("Invalid Size: \"" + s + "\"");
+    }
+
+    /**
+     * Parses the specified string as a size value.
+     * <p>
+     * The ASCII characters {@code \}{@code u002a} ('*') and
+     * {@code \}{@code u0078} ('x') are recognized as separators between
+     * the width and height.</p>
+     * <p>
+     * For any {@code Size s}: {@code Size.parseSize(s.toString()).equals(s)}.
+     * However, the method also handles sizes expressed in the
+     * following forms:</p>
+     * <p>
+     * "<i>width</i>{@code x}<i>height</i>" or
+     * "<i>width</i>{@code *}<i>height</i>" {@code => new Size(width, height)},
+     * where <i>width</i> and <i>height</i> are string integers potentially
+     * containing a sign, such as "-10", "+7" or "5".</p>
+     *
+     * <pre>{@code
+     * Size.parseSize("3*+6").equals(new Size(3, 6)) == true
+     * Size.parseSize("-3x-6").equals(new Size(-3, -6)) == true
+     * Size.parseSize("4 by 3") => throws NumberFormatException
+     * }</pre>
+     *
+     * @param string the string representation of a size value.
+     * @return the size value represented by {@code string}.
+     *
+     * @throws NumberFormatException if {@code string} cannot be parsed
+     * as a size value.
+     * @throws NullPointerException if {@code string} was {@code null}
+     */
+    public static Size parseSize(String string)
+            throws NumberFormatException {
+        checkNotNull(string, "string must not be null");
+
+        int sep_ix = string.indexOf('*');
+        if (sep_ix < 0) {
+            sep_ix = string.indexOf('x');
+        }
+        if (sep_ix < 0) {
+            throw invalidSize(string);
+        }
+        try {
+            return new Size(Integer.parseInt(string.substring(0, sep_ix)),
+                    Integer.parseInt(string.substring(sep_ix + 1)));
+        } catch (NumberFormatException e) {
+            throw invalidSize(string);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        // assuming most sizes are <2^16, doing a rotate will give us perfect hashing
+        return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2)));
+    }
+
+    private final int mWidth;
+    private final int mHeight;
+}
diff --git a/android/util/SizeF.java b/android/util/SizeF.java
new file mode 100644
index 0000000..2edc4a7
--- /dev/null
+++ b/android/util/SizeF.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkArgumentFinite;
+
+/**
+ * Immutable class for describing width and height dimensions in some arbitrary
+ * unit.
+ * <p>
+ * Width and height are finite values stored as a floating point representation.
+ * </p>
+ */
+public final class SizeF {
+    /**
+     * Create a new immutable SizeF instance.
+     *
+     * <p>Both the {@code width} and the {@code height} must be a finite number.
+     * In particular, {@code NaN} and positive/negative infinity are illegal values.</p>
+     *
+     * @param width The width of the size
+     * @param height The height of the size
+     *
+     * @throws IllegalArgumentException
+     *             if either {@code width} or {@code height} was not finite.
+     */
+    public SizeF(final float width, final float height) {
+        mWidth = checkArgumentFinite(width, "width");
+        mHeight = checkArgumentFinite(height, "height");
+    }
+
+    /**
+     * Get the width of the size (as an arbitrary unit).
+     * @return width
+     */
+    public float getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Get the height of the size (as an arbitrary unit).
+     * @return height
+     */
+    public float getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * Check if this size is equal to another size.
+     *
+     * <p>Two sizes are equal if and only if both their widths and heights are the same.</p>
+     *
+     * <p>For this purpose, the width/height float values are considered to be the same if and only
+     * if the method {@link Float#floatToIntBits(float)} returns the identical {@code int} value
+     * when applied to each.</p>
+     *
+     * @return {@code true} if the objects were equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof SizeF) {
+            final SizeF other = (SizeF) obj;
+            return mWidth == other.mWidth && mHeight == other.mHeight;
+        }
+        return false;
+    }
+
+    /**
+     * Return the size represented as a string with the format {@code "WxH"}
+     *
+     * @return string representation of the size
+     */
+    @Override
+    public String toString() {
+        return mWidth + "x" + mHeight;
+    }
+
+    private static NumberFormatException invalidSizeF(String s) {
+        throw new NumberFormatException("Invalid SizeF: \"" + s + "\"");
+    }
+
+    /**
+     * Parses the specified string as a size value.
+     * <p>
+     * The ASCII characters {@code \}{@code u002a} ('*') and
+     * {@code \}{@code u0078} ('x') are recognized as separators between
+     * the width and height.</p>
+     * <p>
+     * For any {@code SizeF s}: {@code SizeF.parseSizeF(s.toString()).equals(s)}.
+     * However, the method also handles sizes expressed in the
+     * following forms:</p>
+     * <p>
+     * "<i>width</i>{@code x}<i>height</i>" or
+     * "<i>width</i>{@code *}<i>height</i>" {@code => new SizeF(width, height)},
+     * where <i>width</i> and <i>height</i> are string floats potentially
+     * containing a sign, such as "-10.3", "+7" or "5.2", but not containing
+     * an {@code 'x'} (such as a float in hexadecimal string format).</p>
+     *
+     * <pre>{@code
+     * SizeF.parseSizeF("3.2*+6").equals(new SizeF(3.2f, 6.0f)) == true
+     * SizeF.parseSizeF("-3x-6").equals(new SizeF(-3.0f, -6.0f)) == true
+     * SizeF.parseSizeF("4 by 3") => throws NumberFormatException
+     * }</pre>
+     *
+     * @param string the string representation of a size value.
+     * @return the size value represented by {@code string}.
+     *
+     * @throws NumberFormatException if {@code string} cannot be parsed
+     * as a size value.
+     * @throws NullPointerException if {@code string} was {@code null}
+     */
+    public static SizeF parseSizeF(String string)
+            throws NumberFormatException {
+        checkNotNull(string, "string must not be null");
+
+        int sep_ix = string.indexOf('*');
+        if (sep_ix < 0) {
+            sep_ix = string.indexOf('x');
+        }
+        if (sep_ix < 0) {
+            throw invalidSizeF(string);
+        }
+        try {
+            return new SizeF(Float.parseFloat(string.substring(0, sep_ix)),
+                    Float.parseFloat(string.substring(sep_ix + 1)));
+        } catch (NumberFormatException e) {
+            throw invalidSizeF(string);
+        } catch (IllegalArgumentException e) {
+            throw invalidSizeF(string);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return Float.floatToIntBits(mWidth) ^ Float.floatToIntBits(mHeight);
+    }
+
+    private final float mWidth;
+    private final float mHeight;
+}
diff --git a/android/util/Slog.java b/android/util/Slog.java
new file mode 100644
index 0000000..2c8bbbf
--- /dev/null
+++ b/android/util/Slog.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
+/**
+ * @hide
+ */
+public final class Slog {
+
+    private Slog() {
+    }
+
+    @UnsupportedAppUsage
+    public static int v(String tag, String msg) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg);
+    }
+
+    public static int v(String tag, String msg, Throwable tr) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    @UnsupportedAppUsage
+    public static int d(String tag, String msg) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg);
+    }
+
+    @UnsupportedAppUsage
+    public static int d(String tag, String msg, Throwable tr) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    @UnsupportedAppUsage
+    public static int i(String tag, String msg) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg);
+    }
+
+    public static int i(String tag, String msg, Throwable tr) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    @UnsupportedAppUsage
+    public static int w(String tag, String msg) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg);
+    }
+
+    @UnsupportedAppUsage
+    public static int w(String tag, String msg, Throwable tr) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int w(String tag, Throwable tr) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, Log.getStackTraceString(tr));
+    }
+
+    @UnsupportedAppUsage
+    public static int e(String tag, String msg) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg);
+    }
+
+    @UnsupportedAppUsage
+    public static int e(String tag, String msg, Throwable tr) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    /**
+     * Like {@link Log#wtf(String, String)}, but will never cause the caller to crash, and
+     * will always be handled asynchronously.  Primarily for use by coding running within
+     * the system process.
+     */
+    @UnsupportedAppUsage
+    public static int wtf(String tag, String msg) {
+        return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true);
+    }
+
+    /**
+     * Like {@link #wtf(String, String)}, but does not output anything to the log.
+     */
+    public static void wtfQuiet(String tag, String msg) {
+        Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true);
+    }
+
+    /**
+     * Like {@link Log#wtfStack(String, String)}, but will never cause the caller to crash, and
+     * will always be handled asynchronously.  Primarily for use by coding running within
+     * the system process.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public static int wtfStack(String tag, String msg) {
+        return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true, true);
+    }
+
+    /**
+     * Like {@link Log#wtf(String, Throwable)}, but will never cause the caller to crash,
+     * and will always be handled asynchronously.  Primarily for use by coding running within
+     * the system process.
+     */
+    public static int wtf(String tag, Throwable tr) {
+        return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true);
+    }
+
+    /**
+     * Like {@link Log#wtf(String, String, Throwable)}, but will never cause the caller to crash,
+     * and will always be handled asynchronously.  Primarily for use by coding running within
+     * the system process.
+     */
+    @UnsupportedAppUsage
+    public static int wtf(String tag, String msg, Throwable tr) {
+        return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true);
+    }
+
+    @UnsupportedAppUsage
+    public static int println(int priority, String tag, String msg) {
+        return Log.println_native(Log.LOG_ID_SYSTEM, priority, tag, msg);
+    }
+}
+
diff --git a/android/util/SparseArray.java b/android/util/SparseArray.java
new file mode 100644
index 0000000..dae760f
--- /dev/null
+++ b/android/util/SparseArray.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
+
+/**
+ * <code>SparseArray</code> maps integers to Objects and, unlike a normal array of Objects,
+ * its indices can contain gaps. <code>SparseArray</code> is intended to be more memory-efficient
+ * than a
+ * <a href="/reference/java/util/HashMap"><code>HashMap</code></a>, because it avoids
+ * auto-boxing keys and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys. The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items. It is generally slower than a
+ * <code>HashMap</code> because lookups require a binary search,
+ * and adds and removes require inserting
+ * and deleting entries in the array. For containers holding up to hundreds of items,
+ * the performance difference is less than 50%.
+ *
+ * <p>To help with performance, the container includes an optimization when removing
+ * keys: instead of compacting its array immediately, it leaves the removed entry marked
+ * as deleted. The entry can then be re-used for the same key or compacted later in
+ * a single garbage collection of all removed entries. This garbage collection
+ * must be performed whenever the array needs to be grown, or when the map size or
+ * entry values are retrieved.
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index returns the
+ * keys in ascending order. In the case of <code>valueAt(int)</code>, the
+ * values corresponding to the keys are returned in ascending order.
+ */
+public class SparseArray<E> implements Cloneable {
+    private static final Object DELETED = new Object();
+    private boolean mGarbage = false;
+
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int)
+    private int[] mKeys;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, E)
+    private Object[] mValues;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
+    private int mSize;
+
+    /**
+     * Creates a new SparseArray containing no mappings.
+     */
+    public SparseArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new SparseArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.  If you supply an initial capacity of 0, the
+     * sparse array will be initialized with a light-weight representation
+     * not requiring any additional array allocations.
+     */
+    public SparseArray(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mKeys = EmptyArray.INT;
+            mValues = EmptyArray.OBJECT;
+        } else {
+            mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
+            mKeys = new int[mValues.length];
+        }
+        mSize = 0;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public SparseArray<E> clone() {
+        SparseArray<E> clone = null;
+        try {
+            clone = (SparseArray<E>) super.clone();
+            clone.mKeys = mKeys.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Returns true if the key exists in the array. This is equivalent to
+     * {@link #indexOfKey(int)} >= 0.
+     *
+     * @param key Potential key in the mapping
+     * @return true if the key is defined in the mapping
+     */
+    public boolean contains(int key) {
+        return indexOfKey(key) >= 0;
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or <code>null</code>
+     * if no such mapping has been made.
+     */
+    public E get(int key) {
+        return get(key, null);
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or the specified Object
+     * if no such mapping has been made.
+     */
+    @SuppressWarnings("unchecked")
+    public E get(int key, E valueIfKeyNotFound) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i < 0 || mValues[i] == DELETED) {
+            return valueIfKeyNotFound;
+        } else {
+            return (E) mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            if (mValues[i] != DELETED) {
+                mValues[i] = DELETED;
+                mGarbage = true;
+            }
+        }
+    }
+
+    /**
+     * @hide
+     * Removes the mapping from the specified key, if there was any, returning the old value.
+     */
+    public E removeReturnOld(int key) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            if (mValues[i] != DELETED) {
+                final E old = (E) mValues[i];
+                mValues[i] = DELETED;
+                mGarbage = true;
+                return old;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Alias for {@link #delete(int)}.
+     */
+    public void remove(int key) {
+        delete(key);
+    }
+
+    /**
+     * Removes the mapping at the specified index.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>,
+     * the behavior is undefined for apps targeting {@link android.os.Build.VERSION_CODES#P} and
+     * earlier, and an {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public void removeAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        if (mValues[index] != DELETED) {
+            mValues[index] = DELETED;
+            mGarbage = true;
+        }
+    }
+
+    /**
+     * Remove a range of mappings as a batch.
+     *
+     * @param index Index to begin at
+     * @param size Number of mappings to remove
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>,
+     * the behavior is undefined.</p>
+     */
+    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);
+
+        int n = mSize;
+        int o = 0;
+        int[] keys = mKeys;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            Object val = values[i];
+
+            if (val != DELETED) {
+                if (i != o) {
+                    keys[o] = keys[i];
+                    values[o] = val;
+                    values[i] = null;
+                }
+
+                o++;
+            }
+        }
+
+        mGarbage = false;
+        mSize = o;
+
+        // Log.e("SparseArray", "gc end with " + mSize);
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, E value) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            if (i < mSize && mValues[i] == DELETED) {
+                mKeys[i] = key;
+                mValues[i] = value;
+                return;
+            }
+
+            if (mGarbage && mSize >= mKeys.length) {
+                gc();
+
+                // Search again because indices may have changed.
+                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
+            }
+
+            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseArray
+     * currently stores.
+     */
+    public int size() {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseArray stores.
+     *
+     * <p>The keys corresponding to indices in ascending order are guaranteed to
+     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+     * smallest key and <code>keyAt(size()-1)</code> will return the largest
+     * key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>,
+     * the behavior is undefined for apps targeting {@link android.os.Build.VERSION_CODES#P} and
+     * earlier, and an {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public int keyAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        if (mGarbage) {
+            gc();
+        }
+
+        return mKeys[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseArray stores.
+     *
+     * <p>The values corresponding to indices in ascending order are guaranteed
+     * to be associated with keys in ascending order, e.g.,
+     * <code>valueAt(0)</code> will return the value associated with the
+     * smallest key and <code>valueAt(size()-1)</code> will return the value
+     * associated with the largest key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>,
+     * the behavior is undefined for apps targeting {@link android.os.Build.VERSION_CODES#P} and
+     * earlier, and an {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    @SuppressWarnings("unchecked")
+    public E valueAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        if (mGarbage) {
+            gc();
+        }
+
+        return (E) mValues[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * SparseArray stores.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public void setValueAt(int index, E value) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        if (mGarbage) {
+            gc();
+        }
+
+        mValues[index] = value;
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified value, or a negative number if no keys map to the
+     * specified value.
+     * <p>Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     * <p>Note also that unlike most collections' {@code indexOf} methods,
+     * this method compares values using {@code ==} rather than {@code equals}.
+     */
+    public int indexOfValue(E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        for (int i = 0; i < mSize; i++) {
+            if (mValues[i] == value) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified value, or a negative number if no keys map to the
+     * specified value.
+     * <p>Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
+     * @hide
+     */
+    public int indexOfValueByValue(E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        for (int i = 0; i < mSize; i++) {
+            if (value == null) {
+                if (mValues[i] == null) {
+                    return i;
+                }
+            } else {
+                if (value.equals(mValues[i])) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseArray.
+     */
+    public void clear() {
+        int n = mSize;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            values[i] = null;
+        }
+
+        mSize = 0;
+        mGarbage = false;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(int key, E value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        if (mGarbage && mSize >= mKeys.length) {
+            gc();
+        }
+
+        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+        mValues = GrowingArrayUtils.append(mValues, mSize, value);
+        mSize++;
+    }
+
+    /**
+     * {@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 "{}";
+        }
+
+        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 {
+                buffer.append("(this Map)");
+            }
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+}
diff --git a/android/util/SparseArrayMap.java b/android/util/SparseArrayMap.java
new file mode 100644
index 0000000..3ec6b81
--- /dev/null
+++ b/android/util/SparseArrayMap.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+
+import java.util.function.Consumer;
+
+/**
+ * A sparse array of ArrayMaps, which is suitable for holding (userId, packageName)->object
+ * associations.
+ *
+ * @param <T> Any class
+ * @hide
+ */
+@TestApi
+public class SparseArrayMap<T> {
+    private final SparseArray<ArrayMap<String, T>> mData = new SparseArray<>();
+
+    /** Add an entry associating obj with the int-String pair. */
+    public void add(int key, @NonNull String mapKey, @Nullable T obj) {
+        ArrayMap<String, T> data = mData.get(key);
+        if (data == null) {
+            data = new ArrayMap<>();
+            mData.put(key, data);
+        }
+        data.put(mapKey, obj);
+    }
+
+    /** Remove all entries from the map. */
+    public void clear() {
+        for (int i = 0; i < mData.size(); ++i) {
+            mData.valueAt(i).clear();
+        }
+    }
+
+    /** Return true if the structure contains an explicit entry for the int-String pair. */
+    public boolean contains(int key, @NonNull String mapKey) {
+        return mData.contains(key) && mData.get(key).containsKey(mapKey);
+    }
+
+    /** Removes all the data for the key, if there was any. */
+    public void delete(int key) {
+        mData.delete(key);
+    }
+
+    /**
+     * Removes the data for the key and mapKey, if there was any.
+     *
+     * @return Returns the value that was stored under the keys, or null if there was none.
+     */
+    @Nullable
+    public T delete(int key, @NonNull String mapKey) {
+        ArrayMap<String, T> data = mData.get(key);
+        if (data != null) {
+            return data.remove(mapKey);
+        }
+        return null;
+    }
+
+    /**
+     * Get the value associated with the int-String pair.
+     */
+    @Nullable
+    public T get(int key, @NonNull String mapKey) {
+        ArrayMap<String, T> data = mData.get(key);
+        if (data != null) {
+            return data.get(mapKey);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the value to which the specified key and mapKey are mapped, or defaultValue if this
+     * map contains no mapping for them.
+     */
+    @Nullable
+    public T getOrDefault(int key, @NonNull String mapKey, T defaultValue) {
+        if (mData.contains(key)) {
+            ArrayMap<String, T> data = mData.get(key);
+            if (data != null && data.containsKey(mapKey)) {
+                return data.get(mapKey);
+            }
+        }
+        return defaultValue;
+    }
+
+    /** @see SparseArray#indexOfKey */
+    public int indexOfKey(int key) {
+        return mData.indexOfKey(key);
+    }
+
+    /**
+     * Returns the index of the mapKey.
+     *
+     * @see SparseArray#indexOfKey
+     */
+    public int indexOfKey(int key, @NonNull String mapKey) {
+        ArrayMap<String, T> data = mData.get(key);
+        if (data != null) {
+            return data.indexOfKey(mapKey);
+        }
+        return -1;
+    }
+
+    /** Returns the key at the given index. */
+    public int keyAt(int index) {
+        return mData.keyAt(index);
+    }
+
+    /** Returns the map's key at the given mapIndex for the given keyIndex. */
+    @NonNull
+    public String keyAt(int keyIndex, int mapIndex) {
+        return mData.valueAt(keyIndex).keyAt(mapIndex);
+    }
+
+    /** Returns the size of the outer array. */
+    public int numMaps() {
+        return mData.size();
+    }
+
+    /** Returns the number of elements in the map of the given key. */
+    public int numElementsForKey(int key) {
+        ArrayMap<String, T> data = mData.get(key);
+        return data == null ? 0 : data.size();
+    }
+
+    /** Returns the value T at the given key and map index. */
+    @Nullable
+    public T valueAt(int keyIndex, int mapIndex) {
+        return mData.valueAt(keyIndex).valueAt(mapIndex);
+    }
+
+    /** Iterate through all int-String pairs and operate on all of the values. */
+    public void forEach(@NonNull Consumer<T> consumer) {
+        for (int i = numMaps() - 1; i >= 0; --i) {
+            ArrayMap<String, T> data = mData.valueAt(i);
+            for (int j = data.size() - 1; j >= 0; --j) {
+                consumer.accept(data.valueAt(j));
+            }
+        }
+    }
+}
diff --git a/android/util/SparseBooleanArray.java b/android/util/SparseBooleanArray.java
new file mode 100644
index 0000000..846df39
--- /dev/null
+++ b/android/util/SparseBooleanArray.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
+
+/**
+ * SparseBooleanArrays map integers to booleans.
+ * Unlike a normal array of booleans
+ * there can be gaps in the indices.  It is intended to be more memory efficient
+ * than using a HashMap to map Integers to Booleans, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys.  The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items.  It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array.  For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)</code>.</p>
+ */
+public class SparseBooleanArray implements Cloneable {
+    /**
+     * Creates a new SparseBooleanArray containing no mappings.
+     */
+    public SparseBooleanArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new SparseBooleanArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.  If you supply an initial capacity of 0, the
+     * sparse array will be initialized with a light-weight representation
+     * not requiring any additional array allocations.
+     */
+    public SparseBooleanArray(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mKeys = EmptyArray.INT;
+            mValues = EmptyArray.BOOLEAN;
+        } else {
+            mKeys = ArrayUtils.newUnpaddedIntArray(initialCapacity);
+            mValues = new boolean[mKeys.length];
+        }
+        mSize = 0;
+    }
+
+    @Override
+    public SparseBooleanArray clone() {
+        SparseBooleanArray clone = null;
+        try {
+            clone = (SparseBooleanArray) super.clone();
+            clone.mKeys = mKeys.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Gets the boolean mapped from the specified key, or <code>false</code>
+     * if no such mapping has been made.
+     */
+    public boolean get(int key) {
+        return get(key, false);
+    }
+
+    /**
+     * Gets the boolean mapped from the specified key, or the specified value
+     * if no such mapping has been made.
+     */
+    public boolean get(int key, boolean valueIfKeyNotFound) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i < 0) {
+            return valueIfKeyNotFound;
+        } else {
+            return mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1));
+            System.arraycopy(mValues, i + 1, mValues, i, mSize - (i + 1));
+            mSize--;
+        }
+    }
+
+    /**
+     * Removes the mapping at the specified index.
+     * <p>
+     * For indices outside of the range {@code 0...size()-1}, the behavior is undefined.
+     */
+    public void removeAt(int index) {
+        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+        mSize--;
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, boolean value) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseBooleanArray
+     * currently stores.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseBooleanArray stores.
+     *
+     * <p>The keys corresponding to indices in ascending order are guaranteed to
+     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+     * smallest key and <code>keyAt(size()-1)</code> will return the largest
+     * key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public int keyAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return mKeys[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseBooleanArray stores.
+     *
+     * <p>The values corresponding to indices in ascending order are guaranteed
+     * to be associated with keys in ascending order, e.g.,
+     * <code>valueAt(0)</code> will return the value associated with the
+     * smallest key and <code>valueAt(size()-1)</code> will return the value
+     * associated with the largest key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public boolean valueAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return mValues[index];
+    }
+
+    /**
+     * Directly set the value at a particular index.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public void setValueAt(int index, boolean value) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        mValues[index] = value;
+    }
+
+    /** @hide */
+    public void setKeyAt(int index, int key) {
+        if (index >= mSize) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        mKeys[index] = key;
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(boolean value) {
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseBooleanArray.
+     */
+    public void clear() {
+        mSize = 0;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(int key, boolean value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+        mValues = GrowingArrayUtils.append(mValues, mSize, value);
+        mSize++;
+    }
+
+    @Override
+    public int hashCode() {
+        int hashCode = mSize;
+        for (int i = 0; i < mSize; i++) {
+            hashCode = 31 * hashCode + mKeys[i] | (mValues[i] ? 1 : 0);
+        }
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(Object that) {
+      if (this == that) {
+          return true;
+      }
+
+      if (!(that instanceof SparseBooleanArray)) {
+          return false;
+      }
+
+      SparseBooleanArray other = (SparseBooleanArray) that;
+      if (mSize != other.mSize) {
+          return false;
+      }
+
+      for (int i = 0; i < mSize; i++) {
+          if (mKeys[i] != other.mKeys[i]) {
+              return false;
+          }
+          if (mValues[i] != other.mValues[i]) {
+              return false;
+          }
+      }
+      return true;
+    }
+
+    /**
+     * {@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();
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int)
+    private int[] mKeys;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, boolean)
+    private boolean[] mValues;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
+    private int mSize;
+}
diff --git a/android/util/SparseIntArray.java b/android/util/SparseIntArray.java
new file mode 100644
index 0000000..d4f6685
--- /dev/null
+++ b/android/util/SparseIntArray.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+
+/**
+ * SparseIntArrays map integers to integers.  Unlike a normal array of integers,
+ * there can be gaps in the indices.  It is intended to be more memory efficient
+ * than using a HashMap to map Integers to Integers, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys.  The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items.  It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array.  For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)</code>.</p>
+ */
+public class SparseIntArray implements Cloneable {
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int)
+    private int[] mKeys;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, int)
+    private int[] mValues;
+    @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
+    private int mSize;
+
+    /**
+     * Creates a new SparseIntArray containing no mappings.
+     */
+    public SparseIntArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new SparseIntArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.  If you supply an initial capacity of 0, the
+     * sparse array will be initialized with a light-weight representation
+     * not requiring any additional array allocations.
+     */
+    public SparseIntArray(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mKeys = EmptyArray.INT;
+            mValues = EmptyArray.INT;
+        } else {
+            mKeys = ArrayUtils.newUnpaddedIntArray(initialCapacity);
+            mValues = new int[mKeys.length];
+        }
+        mSize = 0;
+    }
+
+    @Override
+    public SparseIntArray clone() {
+        SparseIntArray clone = null;
+        try {
+            clone = (SparseIntArray) super.clone();
+            clone.mKeys = mKeys.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Gets the int mapped from the specified key, or <code>0</code>
+     * if no such mapping has been made.
+     */
+    public int get(int key) {
+        return get(key, 0);
+    }
+
+    /**
+     * Gets the int mapped from the specified key, or the specified value
+     * if no such mapping has been made.
+     */
+    public int get(int key, int valueIfKeyNotFound) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i < 0) {
+            return valueIfKeyNotFound;
+        } else {
+            return mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            removeAt(i);
+        }
+    }
+
+    /**
+     * Removes the mapping at the given index.
+     */
+    public void removeAt(int index) {
+        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+        mSize--;
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, int value) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseIntArray
+     * currently stores.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseIntArray stores.
+     *
+     * <p>The keys corresponding to indices in ascending order are guaranteed to
+     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+     * smallest key and <code>keyAt(size()-1)</code> will return the largest
+     * key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public int keyAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return mKeys[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseIntArray stores.
+     *
+     * <p>The values corresponding to indices in ascending order are guaranteed
+     * to be associated with keys in ascending order, e.g.,
+     * <code>valueAt(0)</code> will return the value associated with the
+     * smallest key and <code>valueAt(size()-1)</code> will return the value
+     * associated with the largest key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public int valueAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return mValues[index];
+    }
+
+    /**
+     * Directly set the value at a particular index.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public void setValueAt(int index, int value) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        mValues[index] = value;
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(int value) {
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseIntArray.
+     */
+    public void clear() {
+        mSize = 0;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(int key, int value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+        mValues = GrowingArrayUtils.append(mValues, mSize, value);
+        mSize++;
+    }
+
+    /**
+     * Provides a copy of keys.
+     *
+     * @hide
+     * */
+    public int[] copyKeys() {
+        if (size() == 0) {
+            return null;
+        }
+        return Arrays.copyOf(mKeys, size());
+    }
+
+    /**
+     * {@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/android/util/SparseLongArray.java b/android/util/SparseLongArray.java
new file mode 100644
index 0000000..b0916d3
--- /dev/null
+++ b/android/util/SparseLongArray.java
@@ -0,0 +1,296 @@
+/*
+ * 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 android.util;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
+
+/**
+ * SparseLongArrays map integers to longs.  Unlike a normal array of longs,
+ * there can be gaps in the indices.  It is intended to be more memory efficient
+ * than using a HashMap to map Integers to Longs, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys.  The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items.  It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array.  For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)</code>.</p>
+ */
+public class SparseLongArray implements Cloneable {
+    private int[] mKeys;
+    private long[] mValues;
+    private int mSize;
+
+    /**
+     * Creates a new SparseLongArray containing no mappings.
+     */
+    public SparseLongArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new SparseLongArray containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.  If you supply an initial capacity of 0, the
+     * sparse array will be initialized with a light-weight representation
+     * not requiring any additional array allocations.
+     */
+    public SparseLongArray(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mKeys = EmptyArray.INT;
+            mValues = EmptyArray.LONG;
+        } else {
+            mValues = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+            mKeys = new int[mValues.length];
+        }
+        mSize = 0;
+    }
+
+    @Override
+    public SparseLongArray clone() {
+        SparseLongArray clone = null;
+        try {
+            clone = (SparseLongArray) super.clone();
+            clone.mKeys = mKeys.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Gets the long mapped from the specified key, or <code>0</code>
+     * if no such mapping has been made.
+     */
+    public long get(int key) {
+        return get(key, 0);
+    }
+
+    /**
+     * Gets the long mapped from the specified key, or the specified value
+     * if no such mapping has been made.
+     */
+    public long get(int key, long valueIfKeyNotFound) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i < 0) {
+            return valueIfKeyNotFound;
+        } else {
+            return mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            removeAt(i);
+        }
+    }
+
+    /**
+     * @hide
+     * Remove a range of mappings as a batch.
+     *
+     * @param index Index to begin at
+     * @param size Number of mappings to remove
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>,
+     * the behavior is undefined.</p>
+     */
+    public void removeAtRange(int index, int size) {
+        size = Math.min(size, mSize - index);
+        System.arraycopy(mKeys, index + size, mKeys, index, mSize - (index + size));
+        System.arraycopy(mValues, index + size, mValues, index, mSize - (index + size));
+        mSize -= size;
+    }
+
+    /**
+     * Removes the mapping at the given index.
+     */
+    public void removeAt(int index) {
+        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+        mSize--;
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, long value) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseIntArray
+     * currently stores.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseLongArray stores.
+     *
+     * <p>The keys corresponding to indices in ascending order are guaranteed to
+     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+     * smallest key and <code>keyAt(size()-1)</code> will return the largest
+     * key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public int keyAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return mKeys[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseLongArray stores.
+     *
+     * <p>The values corresponding to indices in ascending order are guaranteed
+     * to be associated with keys in ascending order, e.g.,
+     * <code>valueAt(0)</code> will return the value associated with the
+     * smallest key and <code>valueAt(size()-1)</code> will return the value
+     * associated with the largest key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+     * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+     * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+     * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+     */
+    public long valueAt(int index) {
+        if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+            // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+            // Check if exception should be thrown outside of the critical path.
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return mValues[index];
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(long value) {
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseIntArray.
+     */
+    public void clear() {
+        mSize = 0;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(int key, long value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+        mValues = GrowingArrayUtils.append(mValues, mSize, value);
+        mSize++;
+    }
+
+    /**
+     * {@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();
+    }
+}
diff --git a/android/util/SparseSetArray.java b/android/util/SparseSetArray.java
new file mode 100644
index 0000000..f5025f7
--- /dev/null
+++ b/android/util/SparseSetArray.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 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;
+
+/**
+ * A sparse array of ArraySets, which is suitable to hold userid->packages association.
+ *
+ * @hide
+ */
+public class SparseSetArray<T> {
+    private final SparseArray<ArraySet<T>> mData = new SparseArray<>();
+
+    public SparseSetArray() {
+    }
+
+    /**
+     * Add a value for key n.
+     * @return FALSE when the value already existed for the given key, TRUE otherwise.
+     */
+    public boolean add(int n, T value) {
+        ArraySet<T> set = mData.get(n);
+        if (set == null) {
+            set = new ArraySet<>();
+            mData.put(n, set);
+        }
+        if (set.contains(value)) {
+            return false;
+        }
+        set.add(value);
+        return true;
+    }
+
+    /**
+     * Removes all mappings from this SparseSetArray.
+     */
+    public void clear() {
+        mData.clear();
+    }
+
+    /**
+     * @return whether the value exists for the key n.
+     */
+    public boolean contains(int n, T value) {
+        final ArraySet<T> set = mData.get(n);
+        if (set == null) {
+            return false;
+        }
+        return set.contains(value);
+    }
+
+    /**
+     * @return the set of items of key n
+     */
+    public ArraySet<T> get(int n) {
+        return mData.get(n);
+    }
+
+    /**
+     * Remove a value for key n.
+     * @return TRUE when the value existed for the given key and removed, FALSE otherwise.
+     */
+    public boolean remove(int n, T value) {
+        final ArraySet<T> set = mData.get(n);
+        if (set == null) {
+            return false;
+        }
+        final boolean ret = set.remove(value);
+        if (set.size() == 0) {
+            mData.remove(n);
+        }
+        return ret;
+    }
+
+    /**
+     * Remove all values for key n.
+     */
+    public void remove(int n) {
+        mData.remove(n);
+    }
+    public int size() {
+        return mData.size();
+    }
+
+    public int keyAt(int index) {
+        return mData.keyAt(index);
+    }
+
+    public int sizeAt(int index) {
+        final ArraySet<T> set = mData.valueAt(index);
+        if (set == null) {
+            return 0;
+        }
+        return set.size();
+    }
+
+    public T valueAt(int intIndex, int valueIndex) {
+        return mData.valueAt(intIndex).valueAt(valueIndex);
+    }
+}
diff --git a/android/util/Spline.java b/android/util/Spline.java
new file mode 100644
index 0000000..1037096
--- /dev/null
+++ b/android/util/Spline.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2012 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;
+
+/**
+ * Performs spline interpolation given a set of control points.
+ * @hide
+ */
+public abstract class Spline {
+
+    /**
+     * Interpolates the value of Y = f(X) for given X.
+     * Clamps X to the domain of the spline.
+     *
+     * @param x The X value.
+     * @return The interpolated Y = f(X) value.
+     */
+    public abstract float interpolate(float x);
+
+    /**
+     * Creates an appropriate spline based on the properties of the control points.
+     *
+     * If the control points are monotonic then the resulting spline will preserve that and
+     * otherwise optimize for error bounds.
+     */
+    public static Spline createSpline(float[] x, float[] y) {
+        if (!isStrictlyIncreasing(x)) {
+            throw new IllegalArgumentException("The control points must all have strictly "
+                    + "increasing X values.");
+        }
+
+        if (isMonotonic(y)) {
+            return createMonotoneCubicSpline(x, y);
+        } else {
+            return createLinearSpline(x, y);
+        }
+    }
+
+    /**
+     * Creates a monotone cubic spline from a given set of control points.
+     *
+     * The spline is guaranteed to pass through each control point exactly.
+     * Moreover, assuming the control points are monotonic (Y is non-decreasing or
+     * non-increasing) then the interpolated values will also be monotonic.
+     *
+     * This function uses the Fritsch-Carlson method for computing the spline parameters.
+     * http://en.wikipedia.org/wiki/Monotone_cubic_interpolation
+     *
+     * @param x The X component of the control points, strictly increasing.
+     * @param y The Y component of the control points, monotonic.
+     * @return
+     *
+     * @throws IllegalArgumentException if the X or Y arrays are null, have
+     * different lengths or have fewer than 2 values.
+     * @throws IllegalArgumentException if the control points are not monotonic.
+     */
+    public static Spline createMonotoneCubicSpline(float[] x, float[] y) {
+        return new MonotoneCubicSpline(x, y);
+    }
+
+    /**
+     * Creates a linear spline from a given set of control points.
+     *
+     * Like a monotone cubic spline, the interpolated curve will be monotonic if the control points
+     * are monotonic.
+     *
+     * @param x The X component of the control points, strictly increasing.
+     * @param y The Y component of the control points.
+     * @return
+     *
+     * @throws IllegalArgumentException if the X or Y arrays are null, have
+     * different lengths or have fewer than 2 values.
+     * @throws IllegalArgumentException if the X components of the control points are not strictly
+     * increasing.
+     */
+    public static Spline createLinearSpline(float[] x, float[] y) {
+        return new LinearSpline(x, y);
+    }
+
+    private static boolean isStrictlyIncreasing(float[] x) {
+        if (x == null || x.length < 2) {
+            throw new IllegalArgumentException("There must be at least two control points.");
+        }
+        float prev = x[0];
+        for (int i = 1; i < x.length; i++) {
+            float curr = x[i];
+            if (curr <= prev) {
+                return false;
+            }
+            prev = curr;
+        }
+        return true;
+    }
+
+    private static boolean isMonotonic(float[] x) {
+        if (x == null || x.length < 2) {
+            throw new IllegalArgumentException("There must be at least two control points.");
+        }
+        float prev = x[0];
+        for (int i = 1; i < x.length; i++) {
+            float curr = x[i];
+            if (curr < prev) {
+                return false;
+            }
+            prev = curr;
+        }
+        return true;
+    }
+
+    public static class MonotoneCubicSpline extends Spline {
+        private float[] mX;
+        private float[] mY;
+        private float[] mM;
+
+        public MonotoneCubicSpline(float[] x, float[] y) {
+            if (x == null || y == null || x.length != y.length || x.length < 2) {
+                throw new IllegalArgumentException("There must be at least two control "
+                        + "points and the arrays must be of equal length.");
+            }
+
+            final int n = x.length;
+            float[] d = new float[n - 1]; // could optimize this out
+            float[] m = new float[n];
+
+            // Compute slopes of secant lines between successive points.
+            for (int i = 0; i < n - 1; i++) {
+                float h = x[i + 1] - x[i];
+                if (h <= 0f) {
+                    throw new IllegalArgumentException("The control points must all "
+                            + "have strictly increasing X values.");
+                }
+                d[i] = (y[i + 1] - y[i]) / h;
+            }
+
+            // Initialize the tangents as the average of the secants.
+            m[0] = d[0];
+            for (int i = 1; i < n - 1; i++) {
+                m[i] = (d[i - 1] + d[i]) * 0.5f;
+            }
+            m[n - 1] = d[n - 2];
+
+            // Update the tangents to preserve monotonicity.
+            for (int i = 0; i < n - 1; i++) {
+                if (d[i] == 0f) { // successive Y values are equal
+                    m[i] = 0f;
+                    m[i + 1] = 0f;
+                } else {
+                    float a = m[i] / d[i];
+                    float b = m[i + 1] / d[i];
+                    if (a < 0f || b < 0f) {
+                        throw new IllegalArgumentException("The control points must have "
+                                + "monotonic Y values.");
+                    }
+                    float h = (float) Math.hypot(a, b);
+                    if (h > 3f) {
+                        float t = 3f / h;
+                        m[i] *= t;
+                        m[i + 1] *= t;
+                    }
+                }
+            }
+
+            mX = x;
+            mY = y;
+            mM = m;
+        }
+
+        @Override
+        public float interpolate(float x) {
+            // Handle the boundary cases.
+            final int n = mX.length;
+            if (Float.isNaN(x)) {
+                return x;
+            }
+            if (x <= mX[0]) {
+                return mY[0];
+            }
+            if (x >= mX[n - 1]) {
+                return mY[n - 1];
+            }
+
+            // Find the index 'i' of the last point with smaller X.
+            // We know this will be within the spline due to the boundary tests.
+            int i = 0;
+            while (x >= mX[i + 1]) {
+                i += 1;
+                if (x == mX[i]) {
+                    return mY[i];
+                }
+            }
+
+            // Perform cubic Hermite spline interpolation.
+            float h = mX[i + 1] - mX[i];
+            float t = (x - mX[i]) / h;
+            return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)
+                    + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;
+        }
+
+        // For debugging.
+        @Override
+        public String toString() {
+            StringBuilder str = new StringBuilder();
+            final int n = mX.length;
+            str.append("MonotoneCubicSpline{[");
+            for (int i = 0; i < n; i++) {
+                if (i != 0) {
+                    str.append(", ");
+                }
+                str.append("(").append(mX[i]);
+                str.append(", ").append(mY[i]);
+                str.append(": ").append(mM[i]).append(")");
+            }
+            str.append("]}");
+            return str.toString();
+        }
+    }
+
+    public static class LinearSpline extends Spline {
+        private final float[] mX;
+        private final float[] mY;
+        private final float[] mM;
+
+        public LinearSpline(float[] x, float[] y) {
+            if (x == null || y == null || x.length != y.length || x.length < 2) {
+                throw new IllegalArgumentException("There must be at least two control "
+                        + "points and the arrays must be of equal length.");
+            }
+            final int N = x.length;
+            mM = new float[N-1];
+            for (int i = 0; i < N-1; i++) {
+                mM[i] = (y[i+1] - y[i]) / (x[i+1] - x[i]);
+            }
+            mX = x;
+            mY = y;
+        }
+
+        @Override
+        public float interpolate(float x) {
+            // Handle the boundary cases.
+            final int n = mX.length;
+            if (Float.isNaN(x)) {
+                return x;
+            }
+            if (x <= mX[0]) {
+                return mY[0];
+            }
+            if (x >= mX[n - 1]) {
+                return mY[n - 1];
+            }
+
+            // Find the index 'i' of the last point with smaller X.
+            // We know this will be within the spline due to the boundary tests.
+            int i = 0;
+            while (x >= mX[i + 1]) {
+                i += 1;
+                if (x == mX[i]) {
+                    return mY[i];
+                }
+            }
+            return mY[i] + mM[i] * (x - mX[i]);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder str = new StringBuilder();
+            final int n = mX.length;
+            str.append("LinearSpline{[");
+            for (int i = 0; i < n; i++) {
+                if (i != 0) {
+                    str.append(", ");
+                }
+                str.append("(").append(mX[i]);
+                str.append(", ").append(mY[i]);
+                if (i < n-1) {
+                    str.append(": ").append(mM[i]);
+                }
+                str.append(")");
+            }
+            str.append("]}");
+            return str.toString();
+        }
+    }
+}
diff --git a/android/util/StateSet.java b/android/util/StateSet.java
new file mode 100644
index 0000000..4bbc0f8
--- /dev/null
+++ b/android/util/StateSet.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2007 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;
+
+import com.android.internal.R;
+
+/**
+ * State sets are arrays of positive ints where each element
+ * represents the state of a {@link android.view.View} (e.g. focused,
+ * selected, visible, etc.).  A {@link android.view.View} may be in
+ * one or more of those states.
+ *
+ * A state spec is an array of signed ints where each element
+ * represents a required (if positive) or an undesired (if negative)
+ * {@link android.view.View} state.
+ *
+ * Utils dealing with state sets.
+ *
+ * In theory we could encapsulate the state set and state spec arrays
+ * and not have static methods here but there is some concern about
+ * performance since these methods are called during view drawing.
+ */
+
+public class StateSet {
+    /**
+     * The order here is very important to
+     * {@link android.view.View#getDrawableState()}
+     */
+    private static final int[][] VIEW_STATE_SETS;
+
+    /** @hide */
+    public static final int VIEW_STATE_WINDOW_FOCUSED = 1;
+    /** @hide */
+    public static final int VIEW_STATE_SELECTED = 1 << 1;
+    /** @hide */
+    public static final int VIEW_STATE_FOCUSED = 1 << 2;
+    /** @hide */
+    public static final int VIEW_STATE_ENABLED = 1 << 3;
+    /** @hide */
+    public static final int VIEW_STATE_PRESSED = 1 << 4;
+    /** @hide */
+    public static final int VIEW_STATE_ACTIVATED = 1 << 5;
+    /** @hide */
+    public static final int VIEW_STATE_ACCELERATED = 1 << 6;
+    /** @hide */
+    public static final int VIEW_STATE_HOVERED = 1 << 7;
+    /** @hide */
+    public static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;
+    /** @hide */
+    public static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;
+
+    static final int[] VIEW_STATE_IDS = new int[] {
+            R.attr.state_window_focused,    VIEW_STATE_WINDOW_FOCUSED,
+            R.attr.state_selected,          VIEW_STATE_SELECTED,
+            R.attr.state_focused,           VIEW_STATE_FOCUSED,
+            R.attr.state_enabled,           VIEW_STATE_ENABLED,
+            R.attr.state_pressed,           VIEW_STATE_PRESSED,
+            R.attr.state_activated,         VIEW_STATE_ACTIVATED,
+            R.attr.state_accelerated,       VIEW_STATE_ACCELERATED,
+            R.attr.state_hovered,           VIEW_STATE_HOVERED,
+            R.attr.state_drag_can_accept,   VIEW_STATE_DRAG_CAN_ACCEPT,
+            R.attr.state_drag_hovered,      VIEW_STATE_DRAG_HOVERED
+    };
+
+    static {
+        if ((VIEW_STATE_IDS.length / 2) != R.styleable.ViewDrawableStates.length) {
+            throw new IllegalStateException(
+                    "VIEW_STATE_IDs array length does not match ViewDrawableStates style array");
+        }
+
+        final int[] orderedIds = new int[VIEW_STATE_IDS.length];
+        for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) {
+            final int viewState = R.styleable.ViewDrawableStates[i];
+            for (int j = 0; j < VIEW_STATE_IDS.length; j += 2) {
+                if (VIEW_STATE_IDS[j] == viewState) {
+                    orderedIds[i * 2] = viewState;
+                    orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1];
+                }
+            }
+        }
+
+        final int NUM_BITS = VIEW_STATE_IDS.length / 2;
+        VIEW_STATE_SETS = new int[1 << NUM_BITS][];
+        for (int i = 0; i < VIEW_STATE_SETS.length; i++) {
+            final int numBits = Integer.bitCount(i);
+            final int[] set = new int[numBits];
+            int pos = 0;
+            for (int j = 0; j < orderedIds.length; j += 2) {
+                if ((i & orderedIds[j + 1]) != 0) {
+                    set[pos++] = orderedIds[j];
+                }
+            }
+            VIEW_STATE_SETS[i] = set;
+        }
+    }
+
+    /** @hide */
+    public static int[] get(int mask) {
+        if (mask >= VIEW_STATE_SETS.length) {
+            throw new IllegalArgumentException("Invalid state set mask");
+        }
+        return VIEW_STATE_SETS[mask];
+    }
+
+    /** @hide */
+    public StateSet() {}
+
+    /**
+     * A state specification that will be matched by all StateSets.
+     */
+    public static final int[] WILD_CARD = new int[0];
+
+    /**
+     * A state set that does not contain any valid states.
+     */
+    public static final int[] NOTHING = new int[] { 0 };
+
+    /**
+     * Return whether the stateSetOrSpec is matched by all StateSets.
+     *
+     * @param stateSetOrSpec a state set or state spec.
+     */
+    public static boolean isWildCard(int[] stateSetOrSpec) {
+        return stateSetOrSpec.length == 0 || stateSetOrSpec[0] == 0;
+    }
+
+    /**
+     * Return whether the stateSet matches the desired stateSpec.
+     *
+     * @param stateSpec an array of required (if positive) or
+     *        prohibited (if negative) {@link android.view.View} states.
+     * @param stateSet an array of {@link android.view.View} states
+     */
+    public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) {
+        if (stateSet == null) {
+            return (stateSpec == null || isWildCard(stateSpec));
+        }
+        int stateSpecSize = stateSpec.length;
+        int stateSetSize = stateSet.length;
+        for (int i = 0; i < stateSpecSize; i++) {
+            int stateSpecState = stateSpec[i];
+            if (stateSpecState == 0) {
+                // We've reached the end of the cases to match against.
+                return true;
+            }
+            final boolean mustMatch;
+            if (stateSpecState > 0) {
+                mustMatch = true;
+            } else {
+                // We use negative values to indicate must-NOT-match states.
+                mustMatch = false;
+                stateSpecState = -stateSpecState;
+            }
+            boolean found = false;
+            for (int j = 0; j < stateSetSize; j++) {
+                final int state = stateSet[j];
+                if (state == 0) {
+                    // We've reached the end of states to match.
+                    if (mustMatch) {
+                        // We didn't find this must-match state.
+                        return false;
+                    } else {
+                        // Continue checking other must-not-match states.
+                        break;
+                    }
+                }
+                if (state == stateSpecState) {
+                    if (mustMatch) {
+                        found = true;
+                        // Continue checking other other must-match states.
+                        break;
+                    } else {
+                        // Any match of a must-not-match state returns false.
+                        return false;
+                    }
+                }
+            }
+            if (mustMatch && !found) {
+                // We've reached the end of states to match and we didn't
+                // find a must-match state.
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return whether the state matches the desired stateSpec.
+     *
+     * @param stateSpec an array of required (if positive) or
+     *        prohibited (if negative) {@link android.view.View} states.
+     * @param state a {@link android.view.View} state
+     */
+    public static boolean stateSetMatches(int[] stateSpec, int state) {
+        int stateSpecSize = stateSpec.length;
+        for (int i = 0; i < stateSpecSize; i++) {
+            int stateSpecState = stateSpec[i];
+            if (stateSpecState == 0) {
+                // We've reached the end of the cases to match against.
+                return true;
+            }
+            if (stateSpecState > 0) {
+                if (state != stateSpecState) {
+                   return false;
+                }
+            } else {
+                // We use negative values to indicate must-NOT-match states.
+                if (state == -stateSpecState) {
+                    // We matched a must-not-match case.
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check whether a list of state specs has an attribute specified.
+     * @param stateSpecs a list of state specs we're checking.
+     * @param attr an attribute we're looking for.
+     * @return {@code true} if the attribute is contained in the state specs.
+     * @hide
+     */
+    public static boolean containsAttribute(int[][] stateSpecs, int attr) {
+        if (stateSpecs != null) {
+            for (int[] spec : stateSpecs) {
+                if (spec == null) {
+                    break;
+                }
+                for (int specAttr : spec) {
+                    if (specAttr == attr || -specAttr == attr) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    public static int[] trimStateSet(int[] states, int newSize) {
+        if (states.length == newSize) {
+            return states;
+        }
+
+        int[] trimmedStates = new int[newSize];
+        System.arraycopy(states, 0, trimmedStates, 0, newSize);
+        return trimmedStates;
+    }
+
+    public static String dump(int[] states) {
+        StringBuilder sb = new StringBuilder();
+
+        int count = states.length;
+        for (int i = 0; i < count; i++) {
+
+            switch (states[i]) {
+            case R.attr.state_window_focused:
+                sb.append("W ");
+                break;
+            case R.attr.state_pressed:
+                sb.append("P ");
+                break;
+            case R.attr.state_selected:
+                sb.append("S ");
+                break;
+            case R.attr.state_focused:
+                sb.append("F ");
+                break;
+            case R.attr.state_enabled:
+                sb.append("E ");
+                break;
+            case R.attr.state_checked:
+                sb.append("C ");
+                break;
+            case R.attr.state_activated:
+                sb.append("A ");
+                break;
+            }
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/android/util/StatsEvent.java b/android/util/StatsEvent.java
new file mode 100644
index 0000000..8bd36a5
--- /dev/null
+++ b/android/util/StatsEvent.java
@@ -0,0 +1,845 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * StatsEvent builds and stores the buffer sent over the statsd socket.
+ * This class defines and encapsulates the socket protocol.
+ *
+ * <p>Usage:</p>
+ * <pre>
+ *      // Pushed event
+ *      StatsEvent statsEvent = StatsEvent.newBuilder()
+ *          .setAtomId(atomId)
+ *          .writeBoolean(false)
+ *          .writeString("annotated String field")
+ *          .addBooleanAnnotation(annotationId, true)
+ *          .usePooledBuffer()
+ *          .build();
+ *      StatsLog.write(statsEvent);
+ *
+ *      // Pulled event
+ *      StatsEvent statsEvent = StatsEvent.newBuilder()
+ *          .setAtomId(atomId)
+ *          .writeBoolean(false)
+ *          .writeString("annotated String field")
+ *          .addBooleanAnnotation(annotationId, true)
+ *          .build();
+ * </pre>
+ * @hide
+ **/
+@SystemApi
+public final class StatsEvent {
+    // Type Ids.
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_INT = 0x00;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_LONG = 0x01;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_STRING = 0x02;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_LIST = 0x03;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_FLOAT = 0x04;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_BOOLEAN = 0x05;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_BYTE_ARRAY = 0x06;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_OBJECT = 0x07;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_KEY_VALUE_PAIRS = 0x08;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_ATTRIBUTION_CHAIN = 0x09;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final byte TYPE_ERRORS = 0x0F;
+
+    // Error flags.
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_NO_TIMESTAMP = 0x1;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_NO_ATOM_ID = 0x2;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_OVERFLOW = 0x4;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ATTRIBUTION_CHAIN_TOO_LONG = 0x8;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_TOO_MANY_KEY_VALUE_PAIRS = 0x10;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD = 0x20;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_INVALID_ANNOTATION_ID = 0x40;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ANNOTATION_ID_TOO_LARGE = 0x80;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_TOO_MANY_ANNOTATIONS = 0x100;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_TOO_MANY_FIELDS = 0x200;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x1000;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int ERROR_ATOM_ID_INVALID_POSITION = 0x2000;
+
+    // Size limits.
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int MAX_ANNOTATION_COUNT = 15;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int MAX_ATTRIBUTION_NODES = 127;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int MAX_NUM_ELEMENTS = 127;
+
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static final int MAX_KEY_VALUE_PAIRS = 127;
+
+    private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068;
+
+    // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag.
+    // See android_util_StatsLog.cpp.
+    private static final int MAX_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
+
+    private final int mAtomId;
+    private final byte[] mPayload;
+    private Buffer mBuffer;
+    private final int mNumBytes;
+
+    private StatsEvent(final int atomId, @Nullable final Buffer buffer,
+            @NonNull final byte[] payload, final int numBytes) {
+        mAtomId = atomId;
+        mBuffer = buffer;
+        mPayload = payload;
+        mNumBytes = numBytes;
+    }
+
+    /**
+     * Returns a new StatsEvent.Builder for building StatsEvent object.
+     **/
+    @NonNull
+    public static StatsEvent.Builder newBuilder() {
+        return new StatsEvent.Builder(Buffer.obtain());
+    }
+
+    /**
+     * Get the atom Id of the atom encoded in this StatsEvent object.
+     *
+     * @hide
+     **/
+    public int getAtomId() {
+        return mAtomId;
+    }
+
+    /**
+     * Get the byte array that contains the encoded payload that can be sent to statsd.
+     *
+     * @hide
+     **/
+    @NonNull
+    public byte[] getBytes() {
+        return mPayload;
+    }
+
+    /**
+     * Get the number of bytes used to encode the StatsEvent payload.
+     *
+     * @hide
+     **/
+    public int getNumBytes() {
+        return mNumBytes;
+    }
+
+    /**
+     * Recycle resources used by this StatsEvent object.
+     * No actions should be taken on this StatsEvent after release() is called.
+     *
+     * @hide
+     **/
+    public void release() {
+        if (mBuffer != null) {
+            mBuffer.release();
+            mBuffer = null;
+        }
+    }
+
+    /**
+     * Builder for constructing a StatsEvent object.
+     *
+     * <p>This class defines and encapsulates the socket encoding for the buffer.
+     * The write methods must be called in the same order as the order of fields in the
+     * atom definition.</p>
+     *
+     * <p>setAtomId() can be called anytime before build().</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     *     // Atom definition.
+     *     message MyAtom {
+     *         optional int32 field1 = 1;
+     *         optional int64 field2 = 2;
+     *         optional string field3 = 3 [(annotation1) = true];
+     *     }
+     *
+     *     // StatsEvent construction for pushed event.
+     *     StatsEvent.newBuilder()
+     *     StatsEvent statsEvent = StatsEvent.newBuilder()
+     *         .setAtomId(atomId)
+     *         .writeInt(3) // field1
+     *         .writeLong(8L) // field2
+     *         .writeString("foo") // field 3
+     *         .addBooleanAnnotation(annotation1Id, true)
+     *         .usePooledBuffer()
+     *         .build();
+     *
+     *     // StatsEvent construction for pulled event.
+     *     StatsEvent.newBuilder()
+     *     StatsEvent statsEvent = StatsEvent.newBuilder()
+     *         .setAtomId(atomId)
+     *         .writeInt(3) // field1
+     *         .writeLong(8L) // field2
+     *         .writeString("foo") // field 3
+     *         .addBooleanAnnotation(annotation1Id, true)
+     *         .build();
+     * </pre>
+     **/
+    public static final class Builder {
+        // Fixed positions.
+        private static final int POS_NUM_ELEMENTS = 1;
+        private static final int POS_TIMESTAMP_NS = POS_NUM_ELEMENTS + Byte.BYTES;
+        private static final int POS_ATOM_ID = POS_TIMESTAMP_NS + Byte.BYTES + Long.BYTES;
+
+        private final Buffer mBuffer;
+        private long mTimestampNs;
+        private int mAtomId;
+        private byte mCurrentAnnotationCount;
+        private int mPos;
+        private int mPosLastField;
+        private byte mLastType;
+        private int mNumElements;
+        private int mErrorMask;
+        private boolean mUsePooledBuffer = false;
+
+        private Builder(final Buffer buffer) {
+            mBuffer = buffer;
+            mCurrentAnnotationCount = 0;
+            mAtomId = 0;
+            mTimestampNs = SystemClock.elapsedRealtimeNanos();
+            mNumElements = 0;
+
+            // Set mPos to 0 for writing TYPE_OBJECT at 0th position.
+            mPos = 0;
+            writeTypeId(TYPE_OBJECT);
+
+            // Write timestamp.
+            mPos = POS_TIMESTAMP_NS;
+            writeLong(mTimestampNs);
+        }
+
+        /**
+         * Sets the atom id for this StatsEvent.
+         *
+         * This should be called immediately after StatsEvent.newBuilder()
+         * and should only be called once.
+         * Not calling setAtomId will result in ERROR_NO_ATOM_ID.
+         * Calling setAtomId out of order will result in ERROR_ATOM_ID_INVALID_POSITION.
+         **/
+        @NonNull
+        public Builder setAtomId(final int atomId) {
+            if (0 == mAtomId) {
+                mAtomId = atomId;
+
+                if (1 == mNumElements) { // Only timestamp is written so far.
+                    writeInt(atomId);
+                } else {
+                    // setAtomId called out of order.
+                    mErrorMask |= ERROR_ATOM_ID_INVALID_POSITION;
+                }
+            }
+
+            return this;
+        }
+
+        /**
+         * Write a boolean field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeBoolean(final boolean value) {
+            // Write boolean typeId byte followed by boolean byte representation.
+            writeTypeId(TYPE_BOOLEAN);
+            mPos += mBuffer.putBoolean(mPos, value);
+            mNumElements++;
+            return this;
+        }
+
+        /**
+         * Write an integer field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeInt(final int value) {
+            // Write integer typeId byte followed by 4-byte representation of value.
+            writeTypeId(TYPE_INT);
+            mPos += mBuffer.putInt(mPos, value);
+            mNumElements++;
+            return this;
+        }
+
+        /**
+         * Write a long field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeLong(final long value) {
+            // Write long typeId byte followed by 8-byte representation of value.
+            writeTypeId(TYPE_LONG);
+            mPos += mBuffer.putLong(mPos, value);
+            mNumElements++;
+            return this;
+        }
+
+        /**
+         * Write a float field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeFloat(final float value) {
+            // Write float typeId byte followed by 4-byte representation of value.
+            writeTypeId(TYPE_FLOAT);
+            mPos += mBuffer.putFloat(mPos, value);
+            mNumElements++;
+            return this;
+        }
+
+        /**
+         * Write a String field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeString(@NonNull final String value) {
+            // Write String typeId byte, followed by 4-byte representation of number of bytes
+            // in the UTF-8 encoding, followed by the actual UTF-8 byte encoding of value.
+            final byte[] valueBytes = stringToBytes(value);
+            writeByteArray(valueBytes, TYPE_STRING);
+            return this;
+        }
+
+        /**
+         * Write a byte array field to this StatsEvent.
+         **/
+        @NonNull
+        public Builder writeByteArray(@NonNull final byte[] value) {
+            // Write byte array typeId byte, followed by 4-byte representation of number of bytes
+            // in value, followed by the actual byte array.
+            writeByteArray(value, TYPE_BYTE_ARRAY);
+            return this;
+        }
+
+        private void writeByteArray(@NonNull final byte[] value, final byte typeId) {
+            writeTypeId(typeId);
+            final int numBytes = value.length;
+            mPos += mBuffer.putInt(mPos, numBytes);
+            mPos += mBuffer.putByteArray(mPos, value);
+            mNumElements++;
+        }
+
+        /**
+         * Write an attribution chain field to this StatsEvent.
+         *
+         * The sizes of uids and tags must be equal. The AttributionNode at position i is
+         * made up of uids[i] and tags[i].
+         *
+         * @param uids array of uids in the attribution nodes.
+         * @param tags array of tags in the attribution nodes.
+         **/
+        @NonNull
+        public Builder writeAttributionChain(
+                @NonNull final int[] uids, @NonNull final String[] tags) {
+            final byte numUids = (byte) uids.length;
+            final byte numTags = (byte) tags.length;
+
+            if (numUids != numTags) {
+                mErrorMask |= ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL;
+            } else if (numUids > MAX_ATTRIBUTION_NODES) {
+                mErrorMask |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG;
+            } else {
+                // Write attribution chain typeId byte, followed by 1-byte representation of
+                // number of attribution nodes, followed by encoding of each attribution node.
+                writeTypeId(TYPE_ATTRIBUTION_CHAIN);
+                mPos += mBuffer.putByte(mPos, numUids);
+                for (int i = 0; i < numUids; i++) {
+                    // Each uid is encoded as 4-byte representation of its int value.
+                    mPos += mBuffer.putInt(mPos, uids[i]);
+
+                    // Each tag is encoded as 4-byte representation of number of bytes in its
+                    // UTF-8 encoding, followed by the actual UTF-8 bytes.
+                    final byte[] tagBytes = stringToBytes(tags[i]);
+                    mPos += mBuffer.putInt(mPos, tagBytes.length);
+                    mPos += mBuffer.putByteArray(mPos, tagBytes);
+                }
+                mNumElements++;
+            }
+            return this;
+        }
+
+        /**
+         * Write KeyValuePairsAtom entries to this StatsEvent.
+         *
+         * @param intMap Integer key-value pairs.
+         * @param longMap Long key-value pairs.
+         * @param stringMap String key-value pairs.
+         * @param floatMap Float key-value pairs.
+         **/
+        @NonNull
+        public Builder writeKeyValuePairs(
+                @Nullable final SparseIntArray intMap,
+                @Nullable final SparseLongArray longMap,
+                @Nullable final SparseArray<String> stringMap,
+                @Nullable final SparseArray<Float> floatMap) {
+            final int intMapSize = null == intMap ? 0 : intMap.size();
+            final int longMapSize = null == longMap ? 0 : longMap.size();
+            final int stringMapSize = null == stringMap ? 0 : stringMap.size();
+            final int floatMapSize = null == floatMap ? 0 : floatMap.size();
+            final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize;
+
+            if (totalCount > MAX_KEY_VALUE_PAIRS) {
+                mErrorMask |= ERROR_TOO_MANY_KEY_VALUE_PAIRS;
+            } else {
+                writeTypeId(TYPE_KEY_VALUE_PAIRS);
+                mPos += mBuffer.putByte(mPos, (byte) totalCount);
+
+                for (int i = 0; i < intMapSize; i++) {
+                    final int key = intMap.keyAt(i);
+                    final int value = intMap.valueAt(i);
+                    mPos += mBuffer.putInt(mPos, key);
+                    writeTypeId(TYPE_INT);
+                    mPos += mBuffer.putInt(mPos, value);
+                }
+
+                for (int i = 0; i < longMapSize; i++) {
+                    final int key = longMap.keyAt(i);
+                    final long value = longMap.valueAt(i);
+                    mPos += mBuffer.putInt(mPos, key);
+                    writeTypeId(TYPE_LONG);
+                    mPos += mBuffer.putLong(mPos, value);
+                }
+
+                for (int i = 0; i < stringMapSize; i++) {
+                    final int key = stringMap.keyAt(i);
+                    final String value = stringMap.valueAt(i);
+                    mPos += mBuffer.putInt(mPos, key);
+                    writeTypeId(TYPE_STRING);
+                    final byte[] valueBytes = stringToBytes(value);
+                    mPos += mBuffer.putInt(mPos, valueBytes.length);
+                    mPos += mBuffer.putByteArray(mPos, valueBytes);
+                }
+
+                for (int i = 0; i < floatMapSize; i++) {
+                    final int key = floatMap.keyAt(i);
+                    final float value = floatMap.valueAt(i);
+                    mPos += mBuffer.putInt(mPos, key);
+                    writeTypeId(TYPE_FLOAT);
+                    mPos += mBuffer.putFloat(mPos, value);
+                }
+
+                mNumElements++;
+            }
+
+            return this;
+        }
+
+        /**
+         * Write a boolean annotation for the last field written.
+         **/
+        @NonNull
+        public Builder addBooleanAnnotation(
+                final byte annotationId, final boolean value) {
+            // Ensure there's a field written to annotate.
+            if (mNumElements < 2) {
+                mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+            } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
+                mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
+            } else {
+                mPos += mBuffer.putByte(mPos, annotationId);
+                mPos += mBuffer.putByte(mPos, TYPE_BOOLEAN);
+                mPos += mBuffer.putBoolean(mPos, value);
+                mCurrentAnnotationCount++;
+                writeAnnotationCount();
+            }
+
+            return this;
+        }
+
+        /**
+         * Write an integer annotation for the last field written.
+         **/
+        @NonNull
+        public Builder addIntAnnotation(final byte annotationId, final int value) {
+            if (mNumElements < 2) {
+                mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+            } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) {
+                mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS;
+            } else {
+                mPos += mBuffer.putByte(mPos, annotationId);
+                mPos += mBuffer.putByte(mPos, TYPE_INT);
+                mPos += mBuffer.putInt(mPos, value);
+                mCurrentAnnotationCount++;
+                writeAnnotationCount();
+            }
+
+            return this;
+        }
+
+        /**
+         * Indicates to reuse Buffer's byte array as the underlying payload in StatsEvent.
+         * This should be called for pushed events to reduce memory allocations and garbage
+         * collections.
+         **/
+        @NonNull
+        public Builder usePooledBuffer() {
+            mUsePooledBuffer = true;
+            return this;
+        }
+
+        /**
+         * Builds a StatsEvent object with values entered in this Builder.
+         **/
+        @NonNull
+        public StatsEvent build() {
+            if (0L == mTimestampNs) {
+                mErrorMask |= ERROR_NO_TIMESTAMP;
+            }
+            if (0 == mAtomId) {
+                mErrorMask |= ERROR_NO_ATOM_ID;
+            }
+            if (mBuffer.hasOverflowed()) {
+                mErrorMask |= ERROR_OVERFLOW;
+            }
+            if (mNumElements > MAX_NUM_ELEMENTS) {
+                mErrorMask |= ERROR_TOO_MANY_FIELDS;
+            }
+
+            if (0 == mErrorMask) {
+                mBuffer.putByte(POS_NUM_ELEMENTS, (byte) mNumElements);
+            } else {
+                // Write atom id and error mask. Overwrite any annotations for atom Id.
+                mPos = POS_ATOM_ID;
+                mPos += mBuffer.putByte(mPos, TYPE_INT);
+                mPos += mBuffer.putInt(mPos, mAtomId);
+                mPos += mBuffer.putByte(mPos, TYPE_ERRORS);
+                mPos += mBuffer.putInt(mPos, mErrorMask);
+                mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3);
+            }
+
+            final int size = mPos;
+
+            if (mUsePooledBuffer) {
+                return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size);
+            } else {
+                // Create a copy of the buffer with the required number of bytes.
+                final byte[] payload = new byte[size];
+                System.arraycopy(mBuffer.getBytes(), 0, payload, 0, size);
+
+                // Return Buffer instance to the pool.
+                mBuffer.release();
+
+                return new StatsEvent(mAtomId, null, payload, size);
+            }
+        }
+
+        private void writeTypeId(final byte typeId) {
+            mPosLastField = mPos;
+            mLastType = typeId;
+            mCurrentAnnotationCount = 0;
+            final byte encodedId = (byte) (typeId & 0x0F);
+            mPos += mBuffer.putByte(mPos, encodedId);
+        }
+
+        private void writeAnnotationCount() {
+            // Use first 4 bits for annotation count and last 4 bits for typeId.
+            final byte encodedId = (byte) ((mCurrentAnnotationCount << 4) | (mLastType & 0x0F));
+            mBuffer.putByte(mPosLastField, encodedId);
+        }
+
+        @NonNull
+        private static byte[] stringToBytes(@Nullable final String value) {
+            return (null == value ? "" : value).getBytes(UTF_8);
+        }
+    }
+
+    private static final class Buffer {
+        private static Object sLock = new Object();
+
+        @GuardedBy("sLock")
+        private static Buffer sPool;
+
+        private final byte[] mBytes = new byte[MAX_PAYLOAD_SIZE];
+        private boolean mOverflow = false;
+
+        @NonNull
+        private static Buffer obtain() {
+            final Buffer buffer;
+            synchronized (sLock) {
+                buffer = null == sPool ? new Buffer() : sPool;
+                sPool = null;
+            }
+            buffer.reset();
+            return buffer;
+        }
+
+        private Buffer() {
+        }
+
+        @NonNull
+        private byte[] getBytes() {
+            return mBytes;
+        }
+
+        private void release() {
+            synchronized (sLock) {
+                if (null == sPool) {
+                    sPool = this;
+                }
+            }
+        }
+
+        private void reset() {
+            mOverflow = false;
+        }
+
+        private boolean hasOverflowed() {
+            return mOverflow;
+        }
+
+        /**
+         * Checks for available space in the byte array.
+         *
+         * @param index starting position in the buffer to start the check.
+         * @param numBytes number of bytes to check from index.
+         * @return true if space is available, false otherwise.
+         **/
+        private boolean hasEnoughSpace(final int index, final int numBytes) {
+            final boolean result = index + numBytes < MAX_PAYLOAD_SIZE;
+            if (!result) {
+                mOverflow = true;
+            }
+            return result;
+        }
+
+        /**
+         * Writes a byte into the buffer.
+         *
+         * @param index position in the buffer where the byte is written.
+         * @param value the byte to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putByte(final int index, final byte value) {
+            if (hasEnoughSpace(index, Byte.BYTES)) {
+                mBytes[index] = (byte) (value);
+                return Byte.BYTES;
+            }
+            return 0;
+        }
+
+        /**
+         * Writes a boolean into the buffer.
+         *
+         * @param index position in the buffer where the boolean is written.
+         * @param value the boolean to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putBoolean(final int index, final boolean value) {
+            return putByte(index, (byte) (value ? 1 : 0));
+        }
+
+        /**
+         * Writes an integer into the buffer.
+         *
+         * @param index position in the buffer where the integer is written.
+         * @param value the integer to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putInt(final int index, final int value) {
+            if (hasEnoughSpace(index, Integer.BYTES)) {
+                // Use little endian byte order.
+                mBytes[index] = (byte) (value);
+                mBytes[index + 1] = (byte) (value >> 8);
+                mBytes[index + 2] = (byte) (value >> 16);
+                mBytes[index + 3] = (byte) (value >> 24);
+                return Integer.BYTES;
+            }
+            return 0;
+        }
+
+        /**
+         * Writes a long into the buffer.
+         *
+         * @param index position in the buffer where the long is written.
+         * @param value the long to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putLong(final int index, final long value) {
+            if (hasEnoughSpace(index, Long.BYTES)) {
+                // Use little endian byte order.
+                mBytes[index] = (byte) (value);
+                mBytes[index + 1] = (byte) (value >> 8);
+                mBytes[index + 2] = (byte) (value >> 16);
+                mBytes[index + 3] = (byte) (value >> 24);
+                mBytes[index + 4] = (byte) (value >> 32);
+                mBytes[index + 5] = (byte) (value >> 40);
+                mBytes[index + 6] = (byte) (value >> 48);
+                mBytes[index + 7] = (byte) (value >> 56);
+                return Long.BYTES;
+            }
+            return 0;
+        }
+
+        /**
+         * Writes a float into the buffer.
+         *
+         * @param index position in the buffer where the float is written.
+         * @param value the float to write.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putFloat(final int index, final float value) {
+            return putInt(index, Float.floatToIntBits(value));
+        }
+
+        /**
+         * Copies a byte array into the buffer.
+         *
+         * @param index position in the buffer where the byte array is copied.
+         * @param value the byte array to copy.
+         * @return number of bytes written to buffer from this write operation.
+         **/
+        private int putByteArray(final int index, @NonNull final byte[] value) {
+            final int numBytes = value.length;
+            if (hasEnoughSpace(index, numBytes)) {
+                System.arraycopy(value, 0, mBytes, index, numBytes);
+                return numBytes;
+            }
+            return 0;
+        }
+    }
+}
diff --git a/android/util/StatsEventTest.java b/android/util/StatsEventTest.java
new file mode 100644
index 0000000..7b51155
--- /dev/null
+++ b/android/util/StatsEventTest.java
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.os.SystemClock;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.Range;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Internal tests for {@link StatsEvent}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StatsEventTest {
+
+    @Test
+    public void testNoFields() {
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder().usePooledBuffer().build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        final int expectedAtomId = 0;
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get()).isEqualTo(3);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id")
+                .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertWithMessage("Third element is not errors type")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ERRORS);
+
+        final int errorMask = buffer.getInt();
+
+        assertWithMessage("ERROR_NO_ATOM_ID should be the only error in the error mask")
+                .that(errorMask).isEqualTo(StatsEvent.ERROR_NO_ATOM_ID);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testOnlyAtomId() {
+        final int expectedAtomId = 109;
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder()
+                .setAtomId(expectedAtomId)
+                .usePooledBuffer()
+                .build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get()).isEqualTo(2);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id")
+                .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testIntBooleanIntInt() {
+        final int expectedAtomId = 109;
+        final int field1 = 1;
+        final boolean field2 = true;
+        final int field3 = 3;
+        final int field4 = 4;
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder()
+                .setAtomId(expectedAtomId)
+                .writeInt(field1)
+                .writeBoolean(field2)
+                .writeInt(field3)
+                .writeInt(field4)
+                .usePooledBuffer()
+                .build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get()).isEqualTo(6);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id")
+                .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertWithMessage("First field is not Int")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect field 1")
+                .that(buffer.getInt()).isEqualTo(field1);
+
+        assertWithMessage("Second field is not Boolean")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN);
+
+        assertWithMessage("Incorrect field 2")
+                .that(buffer.get()).isEqualTo(1);
+
+        assertWithMessage("Third field is not Int")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect field 3")
+                .that(buffer.getInt()).isEqualTo(field3);
+
+        assertWithMessage("Fourth field is not Int")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect field 4")
+                .that(buffer.getInt()).isEqualTo(field4);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testStringFloatByteArray() {
+        final int expectedAtomId = 109;
+        final String field1 = "Str 1";
+        final float field2 = 9.334f;
+        final byte[] field3 = new byte[] { 56, 23, 89, -120 };
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder()
+                .setAtomId(expectedAtomId)
+                .writeString(field1)
+                .writeFloat(field2)
+                .writeByteArray(field3)
+                .usePooledBuffer()
+                .build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get()).isEqualTo(5);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id")
+                .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertWithMessage("First field is not String")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_STRING);
+
+        final String field1Actual = getStringFromByteBuffer(buffer);
+        assertWithMessage("Incorrect field 1")
+                .that(field1Actual).isEqualTo(field1);
+
+        assertWithMessage("Second field is not Float")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_FLOAT);
+
+        assertWithMessage("Incorrect field 2")
+                .that(buffer.getFloat()).isEqualTo(field2);
+
+        assertWithMessage("Third field is not byte array")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BYTE_ARRAY);
+
+        final byte[] field3Actual = getByteArrayFromByteBuffer(buffer);
+        assertWithMessage("Incorrect field 3")
+                .that(field3Actual).isEqualTo(field3);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testAttributionChainLong() {
+        final int expectedAtomId = 109;
+        final int[] uids = new int[] { 1, 2, 3, 4, 5 };
+        final String[] tags = new String[] { "1", "2", "3", "4", "5" };
+        final long field2 = -230909823L;
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder()
+                .setAtomId(expectedAtomId)
+                .writeAttributionChain(uids, tags)
+                .writeLong(field2)
+                .usePooledBuffer()
+                .build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get()).isEqualTo(4);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id")
+                .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertWithMessage("First field is not Attribution Chain")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ATTRIBUTION_CHAIN);
+
+        assertWithMessage("Incorrect number of attribution nodes")
+                .that(buffer.get()).isEqualTo((byte) uids.length);
+
+        for (int i = 0; i < tags.length; i++) {
+            assertWithMessage("Incorrect uid in Attribution Chain")
+                    .that(buffer.getInt()).isEqualTo(uids[i]);
+
+            final String tag = getStringFromByteBuffer(buffer);
+            assertWithMessage("Incorrect tag in Attribution Chain")
+                    .that(tag).isEqualTo(tags[i]);
+        }
+
+        assertWithMessage("Second field is not Long")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect field 2")
+                .that(buffer.getLong()).isEqualTo(field2);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testKeyValuePairs() {
+        final int expectedAtomId = 109;
+        final SparseIntArray intMap = new SparseIntArray();
+        final SparseLongArray longMap = new SparseLongArray();
+        final SparseArray<String> stringMap = new SparseArray<>();
+        final SparseArray<Float> floatMap = new SparseArray<>();
+        intMap.put(1, -1);
+        intMap.put(2, -2);
+        stringMap.put(3, "abc");
+        stringMap.put(4, "2h");
+        floatMap.put(9, -234.344f);
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder()
+                .setAtomId(expectedAtomId)
+                .writeKeyValuePairs(intMap, longMap, stringMap, floatMap)
+                .usePooledBuffer()
+                .build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get()).isEqualTo(3);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id")
+                .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertWithMessage("First field is not KeyValuePairs")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_KEY_VALUE_PAIRS);
+
+        assertWithMessage("Incorrect number of key value pairs")
+                .that(buffer.get()).isEqualTo(
+                        (byte) (intMap.size() + longMap.size() + stringMap.size()
+                                + floatMap.size()));
+
+        for (int i = 0; i < intMap.size(); i++) {
+            assertWithMessage("Incorrect key in intMap")
+                    .that(buffer.getInt()).isEqualTo(intMap.keyAt(i));
+            assertWithMessage("The type id of the value should be TYPE_INT in intMap")
+                    .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+            assertWithMessage("Incorrect value in intMap")
+                    .that(buffer.getInt()).isEqualTo(intMap.valueAt(i));
+        }
+
+        for (int i = 0; i < longMap.size(); i++) {
+            assertWithMessage("Incorrect key in longMap")
+                    .that(buffer.getInt()).isEqualTo(longMap.keyAt(i));
+            assertWithMessage("The type id of the value should be TYPE_LONG in longMap")
+                    .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+            assertWithMessage("Incorrect value in longMap")
+                    .that(buffer.getLong()).isEqualTo(longMap.valueAt(i));
+        }
+
+        for (int i = 0; i < stringMap.size(); i++) {
+            assertWithMessage("Incorrect key in stringMap")
+                    .that(buffer.getInt()).isEqualTo(stringMap.keyAt(i));
+            assertWithMessage("The type id of the value should be TYPE_STRING in stringMap")
+                    .that(buffer.get()).isEqualTo(StatsEvent.TYPE_STRING);
+            final String value = getStringFromByteBuffer(buffer);
+            assertWithMessage("Incorrect value in stringMap")
+                    .that(value).isEqualTo(stringMap.valueAt(i));
+        }
+
+        for (int i = 0; i < floatMap.size(); i++) {
+            assertWithMessage("Incorrect key in floatMap")
+                    .that(buffer.getInt()).isEqualTo(floatMap.keyAt(i));
+            assertWithMessage("The type id of the value should be TYPE_FLOAT in floatMap")
+                    .that(buffer.get()).isEqualTo(StatsEvent.TYPE_FLOAT);
+            assertWithMessage("Incorrect value in floatMap")
+                    .that(buffer.getFloat()).isEqualTo(floatMap.valueAt(i));
+        }
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testSingleAnnotations() {
+        final int expectedAtomId = 109;
+        final int field1 = 1;
+        final byte field1AnnotationId = 45;
+        final boolean field1AnnotationValue = false;
+        final boolean field2 = true;
+        final byte field2AnnotationId = 1;
+        final int field2AnnotationValue = 23;
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder()
+                .setAtomId(expectedAtomId)
+                .writeInt(field1)
+                .addBooleanAnnotation(field1AnnotationId, field1AnnotationValue)
+                .writeBoolean(field2)
+                .addIntAnnotation(field2AnnotationId, field2AnnotationValue)
+                .usePooledBuffer()
+                .build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get()).isEqualTo(4);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id")
+                .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        final byte field1Header = buffer.get();
+        final int field1AnnotationValueCount = field1Header >> 4;
+        final byte field1Type = (byte) (field1Header & 0x0F);
+        assertWithMessage("First field is not Int")
+                .that(field1Type).isEqualTo(StatsEvent.TYPE_INT);
+        assertWithMessage("First field annotation count is wrong")
+                .that(field1AnnotationValueCount).isEqualTo(1);
+        assertWithMessage("Incorrect field 1")
+                .that(buffer.getInt()).isEqualTo(field1);
+        assertWithMessage("First field's annotation id is wrong")
+                .that(buffer.get()).isEqualTo(field1AnnotationId);
+        assertWithMessage("First field's annotation type is wrong")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN);
+        assertWithMessage("First field's annotation value is wrong")
+                .that(buffer.get()).isEqualTo(field1AnnotationValue ? 1 : 0);
+
+        final byte field2Header = buffer.get();
+        final int field2AnnotationValueCount = field2Header >> 4;
+        final byte field2Type = (byte) (field2Header & 0x0F);
+        assertWithMessage("Second field is not boolean")
+                .that(field2Type).isEqualTo(StatsEvent.TYPE_BOOLEAN);
+        assertWithMessage("Second field annotation count is wrong")
+                .that(field2AnnotationValueCount).isEqualTo(1);
+        assertWithMessage("Incorrect field 2")
+                .that(buffer.get()).isEqualTo(field2 ? 1 : 0);
+        assertWithMessage("Second field's annotation id is wrong")
+                .that(buffer.get()).isEqualTo(field2AnnotationId);
+        assertWithMessage("Second field's annotation type is wrong")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+        assertWithMessage("Second field's annotation value is wrong")
+                .that(buffer.getInt()).isEqualTo(field2AnnotationValue);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testAtomIdAnnotations() {
+        final int expectedAtomId = 109;
+        final byte atomAnnotationId = 84;
+        final int atomAnnotationValue = 9;
+        final int field1 = 1;
+        final byte field1AnnotationId = 45;
+        final boolean field1AnnotationValue = false;
+        final boolean field2 = true;
+        final byte field2AnnotationId = 1;
+        final int field2AnnotationValue = 23;
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder()
+                .setAtomId(expectedAtomId)
+                .addIntAnnotation(atomAnnotationId, atomAnnotationValue)
+                .writeInt(field1)
+                .addBooleanAnnotation(field1AnnotationId, field1AnnotationValue)
+                .writeBoolean(field2)
+                .addIntAnnotation(field2AnnotationId, field2AnnotationValue)
+                .usePooledBuffer()
+                .build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get()).isEqualTo(4);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        final byte atomIdHeader = buffer.get();
+        final int atomIdAnnotationValueCount = atomIdHeader >> 4;
+        final byte atomIdValueType = (byte) (atomIdHeader & 0x0F);
+        assertWithMessage("Second element is not atom id")
+                .that(atomIdValueType).isEqualTo(StatsEvent.TYPE_INT);
+        assertWithMessage("Atom id annotation count is wrong")
+                .that(atomIdAnnotationValueCount).isEqualTo(1);
+        assertWithMessage("Incorrect atom id")
+                .that(buffer.getInt()).isEqualTo(expectedAtomId);
+        assertWithMessage("Atom id's annotation id is wrong")
+                .that(buffer.get()).isEqualTo(atomAnnotationId);
+        assertWithMessage("Atom id's annotation type is wrong")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+        assertWithMessage("Atom id's annotation value is wrong")
+                .that(buffer.getInt()).isEqualTo(atomAnnotationValue);
+
+        final byte field1Header = buffer.get();
+        final int field1AnnotationValueCount = field1Header >> 4;
+        final byte field1Type = (byte) (field1Header & 0x0F);
+        assertWithMessage("First field is not Int")
+                .that(field1Type).isEqualTo(StatsEvent.TYPE_INT);
+        assertWithMessage("First field annotation count is wrong")
+                .that(field1AnnotationValueCount).isEqualTo(1);
+        assertWithMessage("Incorrect field 1")
+                .that(buffer.getInt()).isEqualTo(field1);
+        assertWithMessage("First field's annotation id is wrong")
+                .that(buffer.get()).isEqualTo(field1AnnotationId);
+        assertWithMessage("First field's annotation type is wrong")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN);
+        assertWithMessage("First field's annotation value is wrong")
+                .that(buffer.get()).isEqualTo(field1AnnotationValue ? 1 : 0);
+
+        final byte field2Header = buffer.get();
+        final int field2AnnotationValueCount = field2Header >> 4;
+        final byte field2Type = (byte) (field2Header & 0x0F);
+        assertWithMessage("Second field is not boolean")
+                .that(field2Type).isEqualTo(StatsEvent.TYPE_BOOLEAN);
+        assertWithMessage("Second field annotation count is wrong")
+                .that(field2AnnotationValueCount).isEqualTo(1);
+        assertWithMessage("Incorrect field 2")
+                .that(buffer.get()).isEqualTo(field2 ? 1 : 0);
+        assertWithMessage("Second field's annotation id is wrong")
+                .that(buffer.get()).isEqualTo(field2AnnotationId);
+        assertWithMessage("Second field's annotation type is wrong")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+        assertWithMessage("Second field's annotation value is wrong")
+                .that(buffer.getInt()).isEqualTo(field2AnnotationValue);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    @Test
+    public void testSetAtomIdNotCalledImmediately() {
+        final int expectedAtomId = 109;
+        final int field1 = 25;
+        final boolean field2 = true;
+
+        final long minTimestamp = SystemClock.elapsedRealtimeNanos();
+        final StatsEvent statsEvent = StatsEvent.newBuilder()
+                .writeInt(field1)
+                .setAtomId(expectedAtomId)
+                .writeBoolean(field2)
+                .usePooledBuffer()
+                .build();
+        final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
+
+        assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
+
+        final ByteBuffer buffer =
+                ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
+
+        assertWithMessage("Root element in buffer is not TYPE_OBJECT")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
+
+        assertWithMessage("Incorrect number of elements in root object")
+                .that(buffer.get()).isEqualTo(3);
+
+        assertWithMessage("First element is not timestamp")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
+
+        assertWithMessage("Incorrect timestamp")
+                .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
+
+        assertWithMessage("Second element is not atom id")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
+
+        assertWithMessage("Incorrect atom id")
+                .that(buffer.getInt()).isEqualTo(expectedAtomId);
+
+        assertWithMessage("Third element is not errors type")
+                .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ERRORS);
+
+        final int errorMask = buffer.getInt();
+
+        assertWithMessage("ERROR_ATOM_ID_INVALID_POSITION should be the only error in the mask")
+                .that(errorMask).isEqualTo(StatsEvent.ERROR_ATOM_ID_INVALID_POSITION);
+
+        assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
+
+        statsEvent.release();
+    }
+
+    private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) {
+        final int numBytes = buffer.getInt();
+        byte[] bytes = new byte[numBytes];
+        buffer.get(bytes);
+        return bytes;
+    }
+
+    private static String getStringFromByteBuffer(final ByteBuffer buffer) {
+        final byte[] bytes = getByteArrayFromByteBuffer(buffer);
+        return new String(bytes, UTF_8);
+    }
+}
diff --git a/android/util/StatsLog.java b/android/util/StatsLog.java
new file mode 100644
index 0000000..4eeae57
--- /dev/null
+++ b/android/util/StatsLog.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.IStatsd;
+import android.os.Process;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.StatsdStatsLog;
+
+/**
+ * StatsLog provides an API for developers to send events to statsd. The events can be used to
+ * define custom metrics inside statsd.
+ */
+public final class StatsLog {
+
+    // Load JNI library
+    static {
+        System.loadLibrary("stats_jni");
+    }
+    private static final String TAG = "StatsLog";
+    private static final boolean DEBUG = false;
+    private static final int EXPERIMENT_IDS_FIELD_ID = 1;
+
+    private StatsLog() {
+    }
+
+    /**
+     * Logs a start event.
+     *
+     * @param label developer-chosen label.
+     * @return True if the log request was sent to statsd.
+     */
+    public static boolean logStart(int label) {
+        int callingUid = Process.myUid();
+        StatsdStatsLog.write(
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+                callingUid,
+                label,
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+        return true;
+    }
+
+    /**
+     * Logs a stop event.
+     *
+     * @param label developer-chosen label.
+     * @return True if the log request was sent to statsd.
+     */
+    public static boolean logStop(int label) {
+        int callingUid = Process.myUid();
+        StatsdStatsLog.write(
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+                callingUid,
+                label,
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
+        return true;
+    }
+
+    /**
+     * Logs an event that does not represent a start or stop boundary.
+     *
+     * @param label developer-chosen label.
+     * @return True if the log request was sent to statsd.
+     */
+    public static boolean logEvent(int label) {
+        int callingUid = Process.myUid();
+        StatsdStatsLog.write(
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+                callingUid,
+                label,
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
+        return true;
+    }
+
+    /**
+     * Logs an event for binary push for module updates.
+     *
+     * @param trainName        name of install train.
+     * @param trainVersionCode version code of the train.
+     * @param options          optional flags about this install.
+     *                         The last 3 bits indicate options:
+     *                             0x01: FLAG_REQUIRE_STAGING
+     *                             0x02: FLAG_ROLLBACK_ENABLED
+     *                             0x04: FLAG_REQUIRE_LOW_LATENCY_MONITOR
+     * @param state            current install state. Defined as State enums in
+     *                         BinaryPushStateChanged atom in
+     *                         frameworks/base/cmds/statsd/src/atoms.proto
+     * @param experimentIds    experiment ids.
+     * @return True if the log request was sent to statsd.
+     */
+    @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
+    public static boolean logBinaryPushStateChanged(@NonNull String trainName,
+            long trainVersionCode, int options, int state,
+            @NonNull long[] experimentIds) {
+        ProtoOutputStream proto = new ProtoOutputStream();
+        for (long id : experimentIds) {
+            proto.write(
+                    ProtoOutputStream.FIELD_TYPE_INT64
+                    | ProtoOutputStream.FIELD_COUNT_REPEATED
+                    | EXPERIMENT_IDS_FIELD_ID,
+                    id);
+        }
+        StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED,
+                trainName,
+                trainVersionCode,
+                (options & IStatsd.FLAG_REQUIRE_STAGING) > 0,
+                (options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0,
+                (options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0,
+                state,
+                proto.getBytes(),
+                0,
+                0,
+                false);
+        return true;
+    }
+
+    /**
+     * Write an event to stats log using the raw format.
+     *
+     * @param buffer    The encoded buffer of data to write.
+     * @param size      The number of bytes from the buffer to write.
+     * @hide
+     */
+    // TODO(b/144935988): Mark deprecated.
+    @SystemApi
+    public static void writeRaw(@NonNull byte[] buffer, int size) {
+        // TODO(b/144935988): make this no-op once clients have migrated to StatsEvent.
+        writeImpl(buffer, size, 0);
+    }
+
+    /**
+     * Write an event to stats log using the raw format.
+     *
+     * @param buffer    The encoded buffer of data to write.
+     * @param size      The number of bytes from the buffer to write.
+     * @param atomId    The id of the atom to which the event belongs.
+     */
+    private static native void writeImpl(@NonNull byte[] buffer, int size, int atomId);
+
+    /**
+     * Write an event to stats log using the raw format encapsulated in StatsEvent.
+     * After writing to stats log, release() is called on the StatsEvent object.
+     * No further action should be taken on the StatsEvent object following this call.
+     *
+     * @param statsEvent    The StatsEvent object containing the encoded buffer of data to write.
+     * @hide
+     */
+    @SystemApi
+    public static void write(@NonNull final StatsEvent statsEvent) {
+        writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId());
+        statsEvent.release();
+    }
+
+    private static void enforceDumpCallingPermission(Context context) {
+        context.enforceCallingPermission(android.Manifest.permission.DUMP, "Need DUMP permission.");
+    }
+
+    private static void enforcesageStatsCallingPermission(Context context) {
+        context.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS,
+                "Need PACKAGE_USAGE_STATS permission.");
+    }
+}
diff --git a/android/util/StringBuilderPrinter.java b/android/util/StringBuilderPrinter.java
new file mode 100644
index 0000000..d0fc1e7
--- /dev/null
+++ b/android/util/StringBuilderPrinter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * Implementation of a {@link android.util.Printer} that sends its output
+ * to a {@link StringBuilder}.
+ */
+public class StringBuilderPrinter implements Printer {
+    private final StringBuilder mBuilder;
+    
+    /**
+     * Create a new Printer that sends to a StringBuilder object.
+     * 
+     * @param builder The StringBuilder where you would like output to go.
+     */
+    public StringBuilderPrinter(StringBuilder builder) {
+        mBuilder = builder;
+    }
+    
+    public void println(String x) {
+        mBuilder.append(x);
+        int len = x.length();
+        if (len <= 0 || x.charAt(len-1) != '\n') {
+            mBuilder.append('\n');
+        }
+    }
+}
diff --git a/android/util/SuperNotCalledException.java b/android/util/SuperNotCalledException.java
new file mode 100644
index 0000000..1836142
--- /dev/null
+++ b/android/util/SuperNotCalledException.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * @hide
+ */
+public final class SuperNotCalledException extends AndroidRuntimeException {
+    public SuperNotCalledException(String msg) {
+        super(msg);
+    }
+}
diff --git a/android/util/TimeFormatException.java b/android/util/TimeFormatException.java
new file mode 100644
index 0000000..f520523
--- /dev/null
+++ b/android/util/TimeFormatException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006 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;
+
+public class TimeFormatException extends RuntimeException
+{
+
+    /**
+     * @hide
+     */
+    public TimeFormatException(String s)
+    {
+        super(s);
+    }
+}
+
diff --git a/android/util/TimeUtils.java b/android/util/TimeUtils.java
new file mode 100644
index 0000000..e8d3459
--- /dev/null
+++ b/android/util/TimeUtils.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.SystemClock;
+
+import libcore.timezone.CountryTimeZones;
+import libcore.timezone.CountryTimeZones.TimeZoneMapping;
+import libcore.timezone.TimeZoneFinder;
+import libcore.timezone.ZoneInfoDb;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A class containing utility methods related to time zones.
+ */
+public class TimeUtils {
+    /** @hide */ public TimeUtils() {}
+    /** {@hide} */
+    private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+    /** @hide */
+    public static final SimpleDateFormat sDumpDateFormat =
+            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+    /**
+     * Tries to return a time zone that would have had the specified offset
+     * and DST value at the specified moment in the specified country.
+     * Returns null if no suitable zone could be found.
+     */
+    public static java.util.TimeZone getTimeZone(
+            int offset, boolean dst, long when, String country) {
+
+        android.icu.util.TimeZone icuTimeZone = getIcuTimeZone(offset, dst, when, country);
+        // We must expose a java.util.TimeZone here for API compatibility because this is a public
+        // API method.
+        return icuTimeZone != null ? java.util.TimeZone.getTimeZone(icuTimeZone.getID()) : null;
+    }
+
+    /**
+     * Returns a frozen ICU time zone that has / would have had the specified offset and DST value
+     * at the specified moment in the specified country. Returns null if no suitable zone could be
+     * found.
+     */
+    private static android.icu.util.TimeZone getIcuTimeZone(
+            int offsetMillis, boolean isDst, long whenMillis, String countryIso) {
+        if (countryIso == null) {
+            return null;
+        }
+
+        android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
+        CountryTimeZones countryTimeZones =
+                TimeZoneFinder.getInstance().lookupCountryTimeZones(countryIso);
+        if (countryTimeZones == null) {
+            return null;
+        }
+        CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias(
+                whenMillis, bias, offsetMillis, isDst);
+        return offsetResult != null ? offsetResult.getTimeZone() : null;
+    }
+
+    /**
+     * Returns time zone IDs for time zones known to be associated with a country.
+     *
+     * <p>The list returned may be different from other on-device sources like
+     * {@link android.icu.util.TimeZone#getRegion(String)} as it can be curated to avoid
+     * contentious or obsolete mappings.
+     *
+     * @param countryCode the ISO 3166-1 alpha-2 code for the country as can be obtained using
+     *     {@link java.util.Locale#getCountry()}
+     * @return IDs that can be passed to {@link java.util.TimeZone#getTimeZone(String)} or similar
+     *     methods, or {@code null} if the countryCode is unrecognized
+     */
+    public static @Nullable List<String> getTimeZoneIdsForCountryCode(@NonNull String countryCode) {
+        if (countryCode == null) {
+            throw new NullPointerException("countryCode == null");
+        }
+        TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance();
+        CountryTimeZones countryTimeZones =
+                timeZoneFinder.lookupCountryTimeZones(countryCode.toLowerCase());
+        if (countryTimeZones == null) {
+            return null;
+        }
+
+        List<String> timeZoneIds = new ArrayList<>();
+        for (TimeZoneMapping timeZoneMapping : countryTimeZones.getTimeZoneMappings()) {
+            if (timeZoneMapping.isShownInPicker()) {
+                timeZoneIds.add(timeZoneMapping.getTimeZoneId());
+            }
+        }
+        return Collections.unmodifiableList(timeZoneIds);
+    }
+
+    /**
+     * Returns a String indicating the version of the time zone database currently
+     * in use.  The format of the string is dependent on the underlying time zone
+     * database implementation, but will typically contain the year in which the database
+     * was updated plus a letter from a to z indicating changes made within that year.
+     *
+     * <p>Time zone database updates should be expected to occur periodically due to
+     * political and legal changes that cannot be anticipated in advance.  Therefore,
+     * when computing the UTC time for a future event, applications should be aware that
+     * the results may differ following a time zone database update.  This method allows
+     * applications to detect that a database change has occurred, and to recalculate any
+     * cached times accordingly.
+     *
+     * <p>The time zone database may be assumed to change only when the device runtime
+     * is restarted.  Therefore, it is not necessary to re-query the database version
+     * during the lifetime of an activity.
+     */
+    public static String getTimeZoneDatabaseVersion() {
+        return ZoneInfoDb.getInstance().getVersion();
+    }
+
+    /** @hide Field length that can hold 999 days of time */
+    public static final int HUNDRED_DAY_FIELD_LEN = 19;
+
+    private static final int SECONDS_PER_MINUTE = 60;
+    private static final int SECONDS_PER_HOUR = 60 * 60;
+    private static final int SECONDS_PER_DAY = 24 * 60 * 60;
+
+    /** @hide */
+    public static final long NANOS_PER_MS = 1000000;
+
+    private static final Object sFormatSync = new Object();
+    private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
+    private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
+
+    static private int accumField(int amt, int suffix, boolean always, int zeropad) {
+        if (amt > 999) {
+            int num = 0;
+            while (amt != 0) {
+                num++;
+                amt /= 10;
+            }
+            return num + suffix;
+        } else {
+            if (amt > 99 || (always && zeropad >= 3)) {
+                return 3+suffix;
+            }
+            if (amt > 9 || (always && zeropad >= 2)) {
+                return 2+suffix;
+            }
+            if (always || amt > 0) {
+                return 1+suffix;
+            }
+        }
+        return 0;
+    }
+
+    static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
+            boolean always, int zeropad) {
+        if (always || amt > 0) {
+            final int startPos = pos;
+            if (amt > 999) {
+                int tmp = 0;
+                while (amt != 0 && tmp < sTmpFormatStr.length) {
+                    int dig = amt % 10;
+                    sTmpFormatStr[tmp] = (char)(dig + '0');
+                    tmp++;
+                    amt /= 10;
+                }
+                tmp--;
+                while (tmp >= 0) {
+                    formatStr[pos] = sTmpFormatStr[tmp];
+                    pos++;
+                    tmp--;
+                }
+            } else {
+                if ((always && zeropad >= 3) || amt > 99) {
+                    int dig = amt/100;
+                    formatStr[pos] = (char)(dig + '0');
+                    pos++;
+                    amt -= (dig*100);
+                }
+                if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
+                    int dig = amt/10;
+                    formatStr[pos] = (char)(dig + '0');
+                    pos++;
+                    amt -= (dig*10);
+                }
+                formatStr[pos] = (char)(amt + '0');
+                pos++;
+            }
+            formatStr[pos] = suffix;
+            pos++;
+        }
+        return pos;
+    }
+
+    private static int formatDurationLocked(long duration, int fieldLen) {
+        if (sFormatStr.length < fieldLen) {
+            sFormatStr = new char[fieldLen];
+        }
+
+        char[] formatStr = sFormatStr;
+
+        if (duration == 0) {
+            int pos = 0;
+            fieldLen -= 1;
+            while (pos < fieldLen) {
+                formatStr[pos++] = ' ';
+            }
+            formatStr[pos] = '0';
+            return pos+1;
+        }
+
+        char prefix;
+        if (duration > 0) {
+            prefix = '+';
+        } else {
+            prefix = '-';
+            duration = -duration;
+        }
+
+        int millis = (int)(duration%1000);
+        int seconds = (int) Math.floor(duration / 1000);
+        int days = 0, hours = 0, minutes = 0;
+
+        if (seconds >= SECONDS_PER_DAY) {
+            days = seconds / SECONDS_PER_DAY;
+            seconds -= days * SECONDS_PER_DAY;
+        }
+        if (seconds >= SECONDS_PER_HOUR) {
+            hours = seconds / SECONDS_PER_HOUR;
+            seconds -= hours * SECONDS_PER_HOUR;
+        }
+        if (seconds >= SECONDS_PER_MINUTE) {
+            minutes = seconds / SECONDS_PER_MINUTE;
+            seconds -= minutes * SECONDS_PER_MINUTE;
+        }
+
+        int pos = 0;
+
+        if (fieldLen != 0) {
+            int myLen = accumField(days, 1, false, 0);
+            myLen += accumField(hours, 1, myLen > 0, 2);
+            myLen += accumField(minutes, 1, myLen > 0, 2);
+            myLen += accumField(seconds, 1, myLen > 0, 2);
+            myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
+            while (myLen < fieldLen) {
+                formatStr[pos] = ' ';
+                pos++;
+                myLen++;
+            }
+        }
+
+        formatStr[pos] = prefix;
+        pos++;
+
+        int start = pos;
+        boolean zeropad = fieldLen != 0;
+        pos = printFieldLocked(formatStr, days, 'd', pos, false, 0);
+        pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
+        pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
+        pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
+        pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
+        formatStr[pos] = 's';
+        return pos + 1;
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static void formatDuration(long duration, StringBuilder builder) {
+        synchronized (sFormatSync) {
+            int len = formatDurationLocked(duration, 0);
+            builder.append(sFormatStr, 0, len);
+        }
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static void formatDuration(long duration, StringBuilder builder, int fieldLen) {
+        synchronized (sFormatSync) {
+            int len = formatDurationLocked(duration, fieldLen);
+            builder.append(sFormatStr, 0, len);
+        }
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
+        synchronized (sFormatSync) {
+            int len = formatDurationLocked(duration, fieldLen);
+            pw.print(new String(sFormatStr, 0, len));
+        }
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    @TestApi
+    public static String formatDuration(long duration) {
+        synchronized (sFormatSync) {
+            int len = formatDurationLocked(duration, 0);
+            return new String(sFormatStr, 0, len);
+        }
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public static void formatDuration(long duration, PrintWriter pw) {
+        formatDuration(duration, pw, 0);
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static void formatDuration(long time, long now, PrintWriter pw) {
+        if (time == 0) {
+            pw.print("--");
+            return;
+        }
+        formatDuration(time-now, pw, 0);
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static String formatUptime(long time) {
+        return formatTime(time, SystemClock.uptimeMillis());
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static String formatRealtime(long time) {
+        return formatTime(time, SystemClock.elapsedRealtime());
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static String formatTime(long time, long referenceTime) {
+        long diff = time - referenceTime;
+        if (diff > 0) {
+            return time + " (in " + diff + " ms)";
+        }
+        if (diff < 0) {
+            return time + " (" + -diff + " ms ago)";
+        }
+        return time + " (now)";
+    }
+
+    /**
+     * Convert a System.currentTimeMillis() value to a time of day value like
+     * that printed in logs. MM-DD HH:MM:SS.MMM
+     *
+     * @param millis since the epoch (1/1/1970)
+     * @return String representation of the time.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static String logTimeOfDay(long millis) {
+        Calendar c = Calendar.getInstance();
+        if (millis >= 0) {
+            c.setTimeInMillis(millis);
+            return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c);
+        } else {
+            return Long.toString(millis);
+        }
+    }
+
+    /** {@hide} */
+    public static String formatForLogging(long millis) {
+        if (millis <= 0) {
+            return "unknown";
+        } else {
+            return sLoggingFormat.format(new Date(millis));
+        }
+    }
+
+    /**
+     * Dump a currentTimeMillis style timestamp for dumpsys.
+     *
+     * @hide
+     */
+    public static void dumpTime(PrintWriter pw, long time) {
+        pw.print(sDumpDateFormat.format(new Date(time)));
+    }
+
+    /**
+     * This method is used to find if a clock time is inclusively between two other clock times
+     * @param reference The time of the day we want check if it is between start and end
+     * @param start The start time reference
+     * @param end The end time
+     * @return true if the reference time is between the two clock times, and false otherwise.
+     */
+    public static boolean isTimeBetween(@NonNull LocalTime reference,
+                                        @NonNull LocalTime start,
+                                        @NonNull LocalTime end) {
+        //    ////////E----+-----S////////
+        if ((reference.isBefore(start) && reference.isAfter(end)
+                //    -----+----S//////////E------
+                || (reference.isBefore(end) && reference.isBefore(start) && start.isBefore(end))
+                //    ---------S//////////E---+---
+                || (reference.isAfter(end) && reference.isAfter(start)) && start.isBefore(end))) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Dump a currentTimeMillis style timestamp for dumpsys, with the delta time from now.
+     *
+     * @hide
+     */
+    public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) {
+        pw.print(sDumpDateFormat.format(new Date(time)));
+        if (time == now) {
+            pw.print(" (now)");
+        } else {
+            pw.print(" (");
+            TimeUtils.formatDuration(time, now, pw);
+            pw.print(")");
+        }
+    }}
diff --git a/android/util/TimedRemoteCaller.java b/android/util/TimedRemoteCaller.java
new file mode 100644
index 0000000..100c16e
--- /dev/null
+++ b/android/util/TimedRemoteCaller.java
@@ -0,0 +1,158 @@
+/*
+ * 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;
+
+import android.os.SystemClock;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This is a helper class for making an async one way call and
+ * its async one way response response in a sync fashion within
+ * a timeout. The key idea is to call the remote method with a
+ * sequence number and a callback and then starting to wait for
+ * the response. The remote method calls back with the result and
+ * the sequence number. If the response comes within the timeout
+ * and its sequence number is the one sent in the method invocation,
+ * then the call succeeded. If the response does not come within
+ * the timeout then the call failed.
+ * <p>
+ * Typical usage is:
+ * </p>
+ * <p><pre><code>
+ * public class MyMethodCaller extends TimeoutRemoteCallHelper<Object> {
+ *     // The one way remote method to call.
+ *     private final IRemoteInterface mTarget;
+ *
+ *     // One way callback invoked when the remote method is done.
+ *     private final IRemoteCallback mCallback = new IRemoteCallback.Stub() {
+ *         public void onCompleted(Object result, int sequence) {
+ *             onRemoteMethodResult(result, sequence);
+ *         }
+ *     };
+ *
+ *     public MyMethodCaller(IRemoteInterface target) {
+ *         mTarget = target;
+ *     }
+ *
+ *     public Object onCallMyMethod(Object arg) throws RemoteException {
+ *         final int sequence = onBeforeRemoteCall();
+ *         mTarget.myMethod(arg, sequence);
+ *         return getResultTimed(sequence);
+ *     }
+ * }
+ * </code></pre></p>
+ *
+ * @param <T> The type of the expected result.
+ *
+ * @hide
+ */
+public abstract class TimedRemoteCaller<T> {
+
+    public static final long DEFAULT_CALL_TIMEOUT_MILLIS = 5000;
+
+    private final Object mLock = new Object();
+
+    private final long mCallTimeoutMillis;
+
+    /** The callbacks we are waiting for, key == sequence id, value == 1 */
+    @GuardedBy("mLock")
+    private final SparseIntArray mAwaitedCalls = new SparseIntArray(1);
+
+    /** The callbacks we received but for which the result has not yet been reported */
+    @GuardedBy("mLock")
+    private final SparseArray<T> mReceivedCalls = new SparseArray<>(1);
+
+    @GuardedBy("mLock")
+    private int mSequenceCounter;
+
+    /**
+     * Create a new timed caller.
+     *
+     * @param callTimeoutMillis The time to wait in {@link #getResultTimed} before a timed call will
+     *                          be declared timed out
+     */
+    public TimedRemoteCaller(long callTimeoutMillis) {
+        mCallTimeoutMillis = callTimeoutMillis;
+    }
+
+    /**
+     * Indicate that a timed call will be made.
+     *
+     * @return The sequence id for the call
+     */
+    protected final int onBeforeRemoteCall() {
+        synchronized (mLock) {
+            int sequenceId;
+            do {
+                sequenceId = mSequenceCounter++;
+            } while (mAwaitedCalls.get(sequenceId) != 0);
+
+            mAwaitedCalls.put(sequenceId, 1);
+
+            return sequenceId;
+        }
+    }
+
+    /**
+     * Indicate that the timed call has returned.
+     *
+     * @param result The result of the timed call
+     * @param sequence The sequence id of the call (from {@link #onBeforeRemoteCall()})
+     */
+    protected final void onRemoteMethodResult(T result, int sequence) {
+        synchronized (mLock) {
+            // Do nothing if we do not await the call anymore as it must have timed out
+            boolean containedSequenceId = mAwaitedCalls.get(sequence) != 0;
+            if (containedSequenceId) {
+                mAwaitedCalls.delete(sequence);
+                mReceivedCalls.put(sequence, result);
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Wait until the timed call has returned.
+     *
+     * @param sequence The sequence id of the call (from {@link #onBeforeRemoteCall()})
+     *
+     * @return The result of the timed call (set in {@link #onRemoteMethodResult(Object, int)})
+     */
+    protected final T getResultTimed(int sequence) throws TimeoutException {
+        final long startMillis = SystemClock.uptimeMillis();
+        while (true) {
+            try {
+                synchronized (mLock) {
+                    if (mReceivedCalls.indexOfKey(sequence) >= 0) {
+                        return mReceivedCalls.removeReturnOld(sequence);
+                    }
+                    final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
+                    final long waitMillis = mCallTimeoutMillis - elapsedMillis;
+                    if (waitMillis <= 0) {
+                        mAwaitedCalls.delete(sequence);
+                        throw new TimeoutException("No response for sequence: " + sequence);
+                    }
+                    mLock.wait(waitMillis);
+                }
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+        }
+    }
+}
diff --git a/android/util/TimingLogger.java b/android/util/TimingLogger.java
new file mode 100644
index 0000000..5a4a512
--- /dev/null
+++ b/android/util/TimingLogger.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.ArrayList;
+
+import android.os.SystemClock;
+
+/**
+ * A utility class to help log timings splits throughout a method call.
+ * Typical usage is:
+ *
+ * <pre>
+ *     TimingLogger timings = new TimingLogger(TAG, "methodA");
+ *     // ... do some work A ...
+ *     timings.addSplit("work A");
+ *     // ... do some work B ...
+ *     timings.addSplit("work B");
+ *     // ... do some work C ...
+ *     timings.addSplit("work C");
+ *     timings.dumpToLog();
+ * </pre>
+ *
+ * <p>The dumpToLog call would add the following to the log:</p>
+ *
+ * <pre>
+ *     D/TAG     ( 3459): methodA: begin
+ *     D/TAG     ( 3459): methodA:      9 ms, work A
+ *     D/TAG     ( 3459): methodA:      1 ms, work B
+ *     D/TAG     ( 3459): methodA:      6 ms, work C
+ *     D/TAG     ( 3459): methodA: end, 16 ms
+ * </pre>
+ *
+ * @deprecated Use {@link android.os.Trace}, or
+ *   <a href="https://developer.android.com/studio/profile/benchmark">Android Studio</a>. In
+ *   general, milliseconds is the wrong granularity for method-level tracing. Rounding errors
+ *   can overemphasize cheap operations, or underemphasize repeated operations. This timing
+ *   system also does not take CPU scheduling or frequency into account.
+ */
+@Deprecated
+public class TimingLogger {
+
+    /**
+     * The Log tag to use for checking Log.isLoggable and for
+     * logging the timings.
+     */
+    private String mTag;
+
+    /** A label to be included in every log. */
+    private String mLabel;
+
+    /** Used to track whether Log.isLoggable was enabled at reset time. */
+    private boolean mDisabled;
+
+    /** Stores the time of each split. */
+    ArrayList<Long> mSplits;
+
+    /** Stores the labels for each split. */
+    ArrayList<String> mSplitLabels;
+
+    /**
+     * Create and initialize a TimingLogger object that will log using
+     * the specific tag. If the Log.isLoggable is not enabled to at
+     * least the Log.VERBOSE level for that tag at creation time then
+     * the addSplit and dumpToLog call will do nothing.
+     * @param tag the log tag to use while logging the timings
+     * @param label a string to be displayed with each log
+     */
+    public TimingLogger(String tag, String label) {
+        reset(tag, label);
+    }
+
+    /**
+     * Clear and initialize a TimingLogger object that will log using
+     * the specific tag. If the Log.isLoggable is not enabled to at
+     * least the Log.VERBOSE level for that tag at creation time then
+     * the addSplit and dumpToLog call will do nothing.
+     * @param tag the log tag to use while logging the timings
+     * @param label a string to be displayed with each log
+     */
+    public void reset(String tag, String label) {
+        mTag = tag;
+        mLabel = label;
+        reset();
+    }
+
+    /**
+     * Clear and initialize a TimingLogger object that will log using
+     * the tag and label that was specified previously, either via
+     * the constructor or a call to reset(tag, label). If the
+     * Log.isLoggable is not enabled to at least the Log.VERBOSE
+     * level for that tag at creation time then the addSplit and
+     * dumpToLog call will do nothing.
+     */
+    public void reset() {
+        mDisabled = !Log.isLoggable(mTag, Log.VERBOSE);
+        if (mDisabled) return;
+        if (mSplits == null) {
+            mSplits = new ArrayList<Long>();
+            mSplitLabels = new ArrayList<String>();
+        } else {
+            mSplits.clear();
+            mSplitLabels.clear();
+        }
+        addSplit(null);
+    }
+
+    /**
+     * Add a split for the current time, labeled with splitLabel. If
+     * Log.isLoggable was not enabled to at least the Log.VERBOSE for
+     * the specified tag at construction or reset() time then this
+     * call does nothing.
+     * @param splitLabel a label to associate with this split.
+     */
+    public void addSplit(String splitLabel) {
+        if (mDisabled) return;
+        long now = SystemClock.elapsedRealtime();
+        mSplits.add(now);
+        mSplitLabels.add(splitLabel);
+    }
+
+    /**
+     * Dumps the timings to the log using Log.d(). If Log.isLoggable was
+     * not enabled to at least the Log.VERBOSE for the specified tag at
+     * construction or reset() time then this call does nothing.
+     */
+    public void dumpToLog() {
+        if (mDisabled) return;
+        Log.d(mTag, mLabel + ": begin");
+        final long first = mSplits.get(0);
+        long now = first;
+        for (int i = 1; i < mSplits.size(); i++) {
+            now = mSplits.get(i);
+            final String splitLabel = mSplitLabels.get(i);
+            final long prev = mSplits.get(i - 1);
+
+            Log.d(mTag, mLabel + ":      " + (now - prev) + " ms, " + splitLabel);
+        }
+        Log.d(mTag, mLabel + ": end, " + (now - first) + " ms");
+    }
+}
diff --git a/android/util/TimingsTraceLog.java b/android/util/TimingsTraceLog.java
new file mode 100644
index 0000000..5370645
--- /dev/null
+++ b/android/util/TimingsTraceLog.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.annotation.NonNull;
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.Trace;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Helper class for reporting boot and shutdown timing metrics.
+ *
+ * <p><b>NOTE:</b> This class is not thread-safe. Use a separate copy for other threads.
+ *
+ * @hide
+ */
+public class TimingsTraceLog {
+    // Debug boot time for every step if it's non-user build.
+    private static final boolean DEBUG_BOOT_TIME = !Build.IS_USER;
+
+    // Maximum number of nested calls that are stored
+    private static final int MAX_NESTED_CALLS = 10;
+
+    private final String[] mStartNames;
+    private final long[] mStartTimes;
+
+    private final String mTag;
+    private final long mTraceTag;
+    private final long mThreadId;
+    private final int mMaxNestedCalls;
+
+    private int mCurrentLevel = -1;
+
+    public TimingsTraceLog(String tag, long traceTag) {
+        this(tag, traceTag, DEBUG_BOOT_TIME ? MAX_NESTED_CALLS : -1);
+    }
+
+    @VisibleForTesting
+    public TimingsTraceLog(String tag, long traceTag, int maxNestedCalls) {
+        mTag = tag;
+        mTraceTag = traceTag;
+        mThreadId = Thread.currentThread().getId();
+        mMaxNestedCalls = maxNestedCalls;
+        if (maxNestedCalls > 0) {
+            mStartNames = new String[maxNestedCalls];
+            mStartTimes = new long[maxNestedCalls];
+        } else {
+            mStartNames = null;
+            mStartTimes = null;
+        }
+    }
+
+    /**
+     * Begin tracing named section
+     * @param name name to appear in trace
+     */
+    public void traceBegin(String name) {
+        assertSameThread();
+        Trace.traceBegin(mTraceTag, name);
+
+        if (!DEBUG_BOOT_TIME) return;
+
+        if (mCurrentLevel + 1 >= mMaxNestedCalls) {
+            Slog.w(mTag, "not tracing duration of '" + name + "' because already reached "
+                    + mMaxNestedCalls + " levels");
+            return;
+        }
+
+        mCurrentLevel++;
+        mStartNames[mCurrentLevel] = name;
+        mStartTimes[mCurrentLevel] = SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * End tracing previously {@link #traceBegin(String) started} section.
+     *
+     * <p>Also {@link #logDuration logs} the duration.
+     */
+    public void traceEnd() {
+        assertSameThread();
+        Trace.traceEnd(mTraceTag);
+
+        if (!DEBUG_BOOT_TIME) return;
+
+        if (mCurrentLevel < 0) {
+            Slog.w(mTag, "traceEnd called more times than traceBegin");
+            return;
+        }
+
+        final String name = mStartNames[mCurrentLevel];
+        final long duration = SystemClock.elapsedRealtime() - mStartTimes[mCurrentLevel];
+        mCurrentLevel--;
+
+        logDuration(name, duration);
+    }
+
+    private void assertSameThread() {
+        final Thread currentThread = Thread.currentThread();
+        if (currentThread.getId() != mThreadId) {
+            throw new IllegalStateException("Instance of TimingsTraceLog can only be called from "
+                    + "the thread it was created on (tid: " + mThreadId + "), but was from "
+                    + currentThread.getName() + " (tid: " + currentThread.getId() + ")");
+        }
+    }
+
+    /**
+     * Logs a duration so it can be parsed by external tools for performance reporting.
+     */
+    public void logDuration(String name, long timeMs) {
+        Slog.d(mTag, name + " took to complete: " + timeMs + "ms");
+    }
+
+    /**
+     * Gets the names of the traces that {@link #traceBegin(String) have begun} but
+     * {@link #traceEnd() have not finished} yet.
+     *
+     * <p><b>NOTE:</b> this method is expensive and it should not be used in "production" - it
+     * should only be used for debugging purposes during development (and/or guarded by
+     * static {@code DEBUG} constants that are {@code false}).
+     */
+    @NonNull
+    public final List<String> getUnfinishedTracesForDebug() {
+        if (mStartTimes == null || mCurrentLevel < 0) return Collections.emptyList();
+        final ArrayList<String> list = new ArrayList<>(mCurrentLevel + 1);
+        for (int i = 0; i <= mCurrentLevel; i++) {
+            list.add(mStartNames[i]);
+        }
+        return list;
+    }
+}
diff --git a/android/util/TrustedTime.java b/android/util/TrustedTime.java
new file mode 100644
index 0000000..f41fe85
--- /dev/null
+++ b/android/util/TrustedTime.java
@@ -0,0 +1,67 @@
+/*
+ * 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 android.util;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * Interface that provides trusted time information, possibly coming from an NTP
+ * server.
+ *
+ * @hide
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
+ */
+public interface TrustedTime {
+    /**
+     * Force update with an external trusted time source, returning {@code true}
+     * when successful.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public boolean forceRefresh();
+
+    /**
+     * Check if this instance has cached a response from a trusted time source.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    boolean hasCache();
+
+    /**
+     * Return time since last trusted time source contact, or
+     * {@link Long#MAX_VALUE} if never contacted.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public long getCacheAge();
+
+    /**
+     * Return current time similar to {@link System#currentTimeMillis()},
+     * possibly using a cached authoritative time source.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    long currentTimeMillis();
+}
diff --git a/android/util/TypedValue.java b/android/util/TypedValue.java
new file mode 100644
index 0000000..7f1ee30
--- /dev/null
+++ b/android/util/TypedValue.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.AnyRes;
+import android.content.pm.ActivityInfo.Config;
+
+/**
+ * Container for a dynamically typed data value.  Primarily used with
+ * {@link android.content.res.Resources} for holding resource values.
+ */
+public class TypedValue {
+    /** The value contains no data. */
+    public static final int TYPE_NULL = 0x00;
+
+    /** The <var>data</var> field holds a resource identifier. */
+    public static final int TYPE_REFERENCE = 0x01;
+    /** The <var>data</var> field holds an attribute resource
+     *  identifier (referencing an attribute in the current theme
+     *  style, not a resource entry). */
+    public static final int TYPE_ATTRIBUTE = 0x02;
+    /** The <var>string</var> field holds string data.  In addition, if
+     *  <var>data</var> is non-zero then it is the string block
+     *  index of the string and <var>assetCookie</var> is the set of
+     *  assets the string came from. */
+    public static final int TYPE_STRING = 0x03;
+    /** The <var>data</var> field holds an IEEE 754 floating point number. */
+    public static final int TYPE_FLOAT = 0x04;
+    /** The <var>data</var> field holds a complex number encoding a
+     *  dimension value. */
+    public static final int TYPE_DIMENSION = 0x05;
+    /** The <var>data</var> field holds a complex number encoding a fraction
+     *  of a container. */
+    public static final int TYPE_FRACTION = 0x06;
+
+    /** Identifies the start of plain integer values.  Any type value
+     *  from this to {@link #TYPE_LAST_INT} means the
+     *  <var>data</var> field holds a generic integer value. */
+    public static final int TYPE_FIRST_INT = 0x10;
+
+    /** The <var>data</var> field holds a number that was
+     *  originally specified in decimal. */
+    public static final int TYPE_INT_DEC = 0x10;
+    /** The <var>data</var> field holds a number that was
+     *  originally specified in hexadecimal (0xn). */
+    public static final int TYPE_INT_HEX = 0x11;
+    /** The <var>data</var> field holds 0 or 1 that was originally
+     *  specified as "false" or "true". */
+    public static final int TYPE_INT_BOOLEAN = 0x12;
+
+    /** Identifies the start of integer values that were specified as
+     *  color constants (starting with '#'). */
+    public static final int TYPE_FIRST_COLOR_INT = 0x1c;
+
+    /** The <var>data</var> field holds a color that was originally
+     *  specified as #aarrggbb. */
+    public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
+    /** The <var>data</var> field holds a color that was originally
+     *  specified as #rrggbb. */
+    public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
+    /** The <var>data</var> field holds a color that was originally
+     *  specified as #argb. */
+    public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
+    /** The <var>data</var> field holds a color that was originally
+     *  specified as #rgb. */
+    public static final int TYPE_INT_COLOR_RGB4 = 0x1f;
+
+    /** Identifies the end of integer values that were specified as color
+     *  constants. */
+    public static final int TYPE_LAST_COLOR_INT = 0x1f;
+
+    /** Identifies the end of plain integer values. */
+    public static final int TYPE_LAST_INT = 0x1f;
+
+    /* ------------------------------------------------------------ */
+
+    /** Complex data: bit location of unit information. */
+    public static final int COMPLEX_UNIT_SHIFT = 0;
+    /** Complex data: mask to extract unit information (after shifting by
+     *  {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as
+     *  defined below. */
+    public static final int COMPLEX_UNIT_MASK = 0xf;
+
+    /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */
+    public static final int COMPLEX_UNIT_PX = 0;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent
+     *  Pixels. */
+    public static final int COMPLEX_UNIT_DIP = 1;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel. */
+    public static final int COMPLEX_UNIT_SP = 2;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is in points. */
+    public static final int COMPLEX_UNIT_PT = 3;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is in inches. */
+    public static final int COMPLEX_UNIT_IN = 4;
+    /** {@link #TYPE_DIMENSION} complex unit: Value is in millimeters. */
+    public static final int COMPLEX_UNIT_MM = 5;
+
+    /** {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall
+     *  size. */
+    public static final int COMPLEX_UNIT_FRACTION = 0;
+    /** {@link #TYPE_FRACTION} complex unit: A fraction of the parent size. */
+    public static final int COMPLEX_UNIT_FRACTION_PARENT = 1;
+
+    /** Complex data: where the radix information is, telling where the decimal
+     *  place appears in the mantissa. */
+    public static final int COMPLEX_RADIX_SHIFT = 4;
+    /** Complex data: mask to extract radix information (after shifting by
+     * {@link #COMPLEX_RADIX_SHIFT}). This give us 4 possible fixed point 
+     * representations as defined below. */ 
+    public static final int COMPLEX_RADIX_MASK = 0x3;
+
+    /** Complex data: the mantissa is an integral number -- i.e., 0xnnnnnn.0 */
+    public static final int COMPLEX_RADIX_23p0 = 0;
+    /** Complex data: the mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn */
+    public static final int COMPLEX_RADIX_16p7 = 1;
+    /** Complex data: the mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn */
+    public static final int COMPLEX_RADIX_8p15 = 2;
+    /** Complex data: the mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn */
+    public static final int COMPLEX_RADIX_0p23 = 3;
+
+    /** Complex data: bit location of mantissa information. */
+    public static final int COMPLEX_MANTISSA_SHIFT = 8;
+    /** Complex data: mask to extract mantissa information (after shifting by
+     *  {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision;
+     *  the top bit is the sign. */
+    public static final int COMPLEX_MANTISSA_MASK = 0xffffff;
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * {@link #TYPE_NULL} data indicating the value was not specified.
+     */
+    public static final int DATA_NULL_UNDEFINED = 0;
+    /**
+     * {@link #TYPE_NULL} data indicating the value was explicitly set to null.
+     */
+    public static final int DATA_NULL_EMPTY = 1;
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * If {@link #density} is equal to this value, then the density should be
+     * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}.
+     */
+    public static final int DENSITY_DEFAULT = 0;
+
+    /**
+     * If {@link #density} is equal to this value, then there is no density
+     * associated with the resource and it should not be scaled.
+     */
+    public static final int DENSITY_NONE = 0xffff;
+
+    /* ------------------------------------------------------------ */
+
+    /** The type held by this value, as defined by the constants here.
+     *  This tells you how to interpret the other fields in the object. */
+    public int type;
+
+    /** If the value holds a string, this is it. */
+    public CharSequence string;
+
+    /** Basic data in the value, interpreted according to {@link #type} */
+    public int data;
+
+    /** Additional information about where the value came from; only
+     *  set for strings. */
+    public int assetCookie;
+
+    /** If Value came from a resource, this holds the corresponding resource id. */
+    @AnyRes
+    public int resourceId;
+
+    /**
+     * If the value came from a resource, these are the configurations for
+     * which its contents can change.
+     *
+     * <p>For example, if a resource has a value defined for the -land resource qualifier,
+     * this field will have the {@link android.content.pm.ActivityInfo#CONFIG_ORIENTATION} bit set.
+     * </p>
+     *
+     * @see android.content.pm.ActivityInfo#CONFIG_MCC
+     * @see android.content.pm.ActivityInfo#CONFIG_MNC
+     * @see android.content.pm.ActivityInfo#CONFIG_LOCALE
+     * @see android.content.pm.ActivityInfo#CONFIG_TOUCHSCREEN
+     * @see android.content.pm.ActivityInfo#CONFIG_KEYBOARD
+     * @see android.content.pm.ActivityInfo#CONFIG_KEYBOARD_HIDDEN
+     * @see android.content.pm.ActivityInfo#CONFIG_NAVIGATION
+     * @see android.content.pm.ActivityInfo#CONFIG_ORIENTATION
+     * @see android.content.pm.ActivityInfo#CONFIG_SCREEN_LAYOUT
+     * @see android.content.pm.ActivityInfo#CONFIG_UI_MODE
+     * @see android.content.pm.ActivityInfo#CONFIG_SCREEN_SIZE
+     * @see android.content.pm.ActivityInfo#CONFIG_SMALLEST_SCREEN_SIZE
+     * @see android.content.pm.ActivityInfo#CONFIG_DENSITY
+     * @see android.content.pm.ActivityInfo#CONFIG_LAYOUT_DIRECTION
+     * @see android.content.pm.ActivityInfo#CONFIG_COLOR_MODE
+     *
+     */
+    public @Config int changingConfigurations = -1;
+
+    /**
+     * If the Value came from a resource, this holds the corresponding pixel density.
+     * */
+    public int density;
+
+    /**
+     * If the Value came from a style resource or a layout resource (set in an XML layout), this
+     * holds the corresponding style or layout resource id against which the attribute was resolved.
+     */
+    public int sourceResourceId;
+
+    /* ------------------------------------------------------------ */
+
+    /** Return the data for this value as a float.  Only use for values
+     *  whose type is {@link #TYPE_FLOAT}. */
+    public final float getFloat() {
+        return Float.intBitsToFloat(data);
+    }
+
+    private static final float MANTISSA_MULT =
+        1.0f / (1<<TypedValue.COMPLEX_MANTISSA_SHIFT);
+    private static final float[] RADIX_MULTS = new float[] {
+        1.0f*MANTISSA_MULT, 1.0f/(1<<7)*MANTISSA_MULT,
+        1.0f/(1<<15)*MANTISSA_MULT, 1.0f/(1<<23)*MANTISSA_MULT
+    };
+
+    /**
+     * Determine if a value is a color.
+     *
+     * This works by comparing {@link #type} to {@link #TYPE_FIRST_COLOR_INT}
+     * and {@link #TYPE_LAST_COLOR_INT}.
+     *
+     * @return true if this value is a color
+     */
+    public boolean isColorType() {
+        return (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT);
+    }
+
+    /**
+     * Retrieve the base value from a complex data integer.  This uses the 
+     * {@link #COMPLEX_MANTISSA_MASK} and {@link #COMPLEX_RADIX_MASK} fields of 
+     * the data to compute a floating point representation of the number they 
+     * describe.  The units are ignored. 
+     *  
+     * @param complex A complex data value.
+     * 
+     * @return A floating point value corresponding to the complex data.
+     */
+    public static float complexToFloat(int complex)
+    {
+        return (complex&(TypedValue.COMPLEX_MANTISSA_MASK
+                   <<TypedValue.COMPLEX_MANTISSA_SHIFT))
+            * RADIX_MULTS[(complex>>TypedValue.COMPLEX_RADIX_SHIFT)
+                            & TypedValue.COMPLEX_RADIX_MASK];
+    }
+
+    /**
+     * Converts a complex data value holding a dimension to its final floating 
+     * point value. The given <var>data</var> must be structured as a 
+     * {@link #TYPE_DIMENSION}.
+     *  
+     * @param data A complex data value holding a unit, magnitude, and 
+     *             mantissa.
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * metrics depending on its unit. 
+     */
+    public static float complexToDimension(int data, DisplayMetrics metrics)
+    {
+        return applyDimension(
+            (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
+            complexToFloat(data),
+            metrics);
+    }
+
+    /**
+     * Converts a complex data value holding a dimension to its final value
+     * as an integer pixel offset.  This is the same as
+     * {@link #complexToDimension}, except the raw floating point value is
+     * truncated to an integer (pixel) value.
+     * The given <var>data</var> must be structured as a 
+     * {@link #TYPE_DIMENSION}.
+     *  
+     * @param data A complex data value holding a unit, magnitude, and 
+     *             mantissa.
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The number of pixels specified by the data and its desired
+     * multiplier and units.
+     */
+    public static int complexToDimensionPixelOffset(int data,
+            DisplayMetrics metrics)
+    {
+        return (int)applyDimension(
+                (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
+                complexToFloat(data),
+                metrics);
+    }
+
+    /**
+     * Converts a complex data value holding a dimension to its final value
+     * as an integer pixel size.  This is the same as
+     * {@link #complexToDimension}, except the raw floating point value is
+     * converted to an integer (pixel) value for use as a size.  A size
+     * conversion involves rounding the base value, and ensuring that a
+     * non-zero base value is at least one pixel in size.
+     * The given <var>data</var> must be structured as a 
+     * {@link #TYPE_DIMENSION}.
+     *  
+     * @param data A complex data value holding a unit, magnitude, and 
+     *             mantissa.
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The number of pixels specified by the data and its desired
+     * multiplier and units.
+     */
+    public static int complexToDimensionPixelSize(int data,
+            DisplayMetrics metrics)
+    {
+        final float value = complexToFloat(data);
+        final float f = applyDimension(
+                (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
+                value,
+                metrics);
+        final int res = (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));
+        if (res != 0) return res;
+        if (value == 0) return 0;
+        if (value > 0) return 1;
+        return -1;
+    }
+
+    /**
+     * @hide Was accidentally exposed in API level 1 for debugging purposes.
+     * Kept for compatibility just in case although the debugging code has been removed.
+     */
+    @Deprecated
+    public static float complexToDimensionNoisy(int data, DisplayMetrics metrics)
+    {
+        return complexToDimension(data, metrics);
+    }
+
+    /**
+     * Return the complex unit type for this value. For example, a dimen type
+     * with value 12sp will return {@link #COMPLEX_UNIT_SP}. Only use for values
+     * whose type is {@link #TYPE_DIMENSION}.
+     *
+     * @return The complex unit type.
+     */
+     public int getComplexUnit()
+     {
+         return COMPLEX_UNIT_MASK & (data>>TypedValue.COMPLEX_UNIT_SHIFT);
+     }
+
+    /**
+     * Converts an unpacked complex data value holding a dimension to its final floating 
+     * point value. The two parameters <var>unit</var> and <var>value</var>
+     * are as in {@link #TYPE_DIMENSION}.
+     *  
+     * @param unit The unit to convert from.
+     * @param value The value to apply the unit to.
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * metrics depending on its unit. 
+     */
+    public static float applyDimension(int unit, float value,
+                                       DisplayMetrics metrics)
+    {
+        switch (unit) {
+        case COMPLEX_UNIT_PX:
+            return value;
+        case COMPLEX_UNIT_DIP:
+            return value * metrics.density;
+        case COMPLEX_UNIT_SP:
+            return value * metrics.scaledDensity;
+        case COMPLEX_UNIT_PT:
+            return value * metrics.xdpi * (1.0f/72);
+        case COMPLEX_UNIT_IN:
+            return value * metrics.xdpi;
+        case COMPLEX_UNIT_MM:
+            return value * metrics.xdpi * (1.0f/25.4f);
+        }
+        return 0;
+    }
+
+    /**
+     * Return the data for this value as a dimension.  Only use for values 
+     * whose type is {@link #TYPE_DIMENSION}. 
+     * 
+     * @param metrics Current display metrics to use in the conversion -- 
+     *                supplies display density and scaling information.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * metrics depending on its unit. 
+     */
+    public float getDimension(DisplayMetrics metrics)
+    {
+        return complexToDimension(data, metrics);
+    }
+
+    /**
+     * Converts a complex data value holding a fraction to its final floating 
+     * point value. The given <var>data</var> must be structured as a 
+     * {@link #TYPE_FRACTION}.
+     * 
+     * @param data A complex data value holding a unit, magnitude, and 
+     *             mantissa.
+     * @param base The base value of this fraction.  In other words, a 
+     *             standard fraction is multiplied by this value.
+     * @param pbase The parent base value of this fraction.  In other 
+     *             words, a parent fraction (nn%p) is multiplied by this
+     *             value.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * base value depending on its unit. 
+     */
+    public static float complexToFraction(int data, float base, float pbase)
+    {
+        switch ((data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK) {
+        case COMPLEX_UNIT_FRACTION:
+            return complexToFloat(data) * base;
+        case COMPLEX_UNIT_FRACTION_PARENT:
+            return complexToFloat(data) * pbase;
+        }
+        return 0;
+    }
+
+    /**
+     * Return the data for this value as a fraction.  Only use for values whose 
+     * type is {@link #TYPE_FRACTION}. 
+     * 
+     * @param base The base value of this fraction.  In other words, a 
+     *             standard fraction is multiplied by this value.
+     * @param pbase The parent base value of this fraction.  In other 
+     *             words, a parent fraction (nn%p) is multiplied by this
+     *             value.
+     * 
+     * @return The complex floating point value multiplied by the appropriate 
+     * base value depending on its unit. 
+     */
+    public float getFraction(float base, float pbase)
+    {
+        return complexToFraction(data, base, pbase);
+    }
+
+    /**
+     * Regardless of the actual type of the value, try to convert it to a
+     * string value.  For example, a color type will be converted to a
+     * string of the form #aarrggbb.
+     * 
+     * @return CharSequence The coerced string value.  If the value is
+     *         null or the type is not known, null is returned.
+     */
+    public final CharSequence coerceToString()
+    {
+        int t = type;
+        if (t == TYPE_STRING) {
+            return string;
+        }
+        return coerceToString(t, data);
+    }
+
+    private static final String[] DIMENSION_UNIT_STRS = new String[] {
+        "px", "dip", "sp", "pt", "in", "mm"
+    };
+    private static final String[] FRACTION_UNIT_STRS = new String[] {
+        "%", "%p"
+    };
+
+    /**
+     * Perform type conversion as per {@link #coerceToString()} on an
+     * explicitly supplied type and data.
+     * 
+     * @param type The data type identifier.
+     * @param data The data value.
+     * 
+     * @return String The coerced string value.  If the value is
+     *         null or the type is not known, null is returned.
+     */
+    public static final String coerceToString(int type, int data)
+    {
+        switch (type) {
+        case TYPE_NULL:
+            return null;
+        case TYPE_REFERENCE:
+            return "@" + data;
+        case TYPE_ATTRIBUTE:
+            return "?" + data;
+        case TYPE_FLOAT:
+            return Float.toString(Float.intBitsToFloat(data));
+        case TYPE_DIMENSION:
+            return Float.toString(complexToFloat(data)) + DIMENSION_UNIT_STRS[
+                (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK];
+        case TYPE_FRACTION:
+            return Float.toString(complexToFloat(data)*100) + FRACTION_UNIT_STRS[
+                (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK];
+        case TYPE_INT_HEX:
+            return "0x" + Integer.toHexString(data);
+        case TYPE_INT_BOOLEAN:
+            return data != 0 ? "true" : "false";
+        }
+
+        if (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT) {
+            return "#" + Integer.toHexString(data);
+        } else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) {
+            return Integer.toString(data);
+        }
+
+        return null;
+    }
+
+    public void setTo(TypedValue other)
+    {
+        type = other.type;
+        string = other.string;
+        data = other.data;
+        assetCookie = other.assetCookie;
+        resourceId = other.resourceId;
+        density = other.density;
+    }
+
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("TypedValue{t=0x").append(Integer.toHexString(type));
+        sb.append("/d=0x").append(Integer.toHexString(data));
+        if (type == TYPE_STRING) {
+            sb.append(" \"").append(string != null ? string : "<null>").append("\"");
+        }
+        if (assetCookie != 0) {
+            sb.append(" a=").append(assetCookie);
+        }
+        if (resourceId != 0) {
+            sb.append(" r=0x").append(Integer.toHexString(resourceId));
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+};
+
diff --git a/android/util/UtilConfig.java b/android/util/UtilConfig.java
new file mode 100644
index 0000000..7658c40
--- /dev/null
+++ b/android/util/UtilConfig.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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 to configure several of the util classes.
+ *
+ * @hide
+ */
+public class UtilConfig {
+    static boolean sThrowExceptionForUpperArrayOutOfBounds = true;
+
+    public static void setThrowExceptionForUpperArrayOutOfBounds(boolean check) {
+        sThrowExceptionForUpperArrayOutOfBounds = check;
+    }
+}
diff --git a/android/util/Xml.java b/android/util/Xml.java
new file mode 100644
index 0000000..e3b8fec
--- /dev/null
+++ b/android/util/Xml.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2007 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;
+
+import libcore.util.XmlObjectFactory;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * XML utility methods.
+ */
+public class Xml {
+    private Xml() {}
+
+    /**
+     * {@link org.xmlpull.v1.XmlPullParser} "relaxed" feature name.
+     *
+     * @see <a href="http://xmlpull.org/v1/doc/features.html#relaxed">
+     *  specification</a>
+     */
+    public static String FEATURE_RELAXED = "http://xmlpull.org/v1/doc/features.html#relaxed";
+
+    /**
+     * Parses the given xml string and fires events on the given SAX handler.
+     */
+    public static void parse(String xml, ContentHandler contentHandler)
+            throws SAXException {
+        try {
+            XMLReader reader = XmlObjectFactory.newXMLReader();
+            reader.setContentHandler(contentHandler);
+            reader.parse(new InputSource(new StringReader(xml)));
+        } catch (IOException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * Parses xml from the given reader and fires events on the given SAX
+     * handler.
+     */
+    public static void parse(Reader in, ContentHandler contentHandler)
+            throws IOException, SAXException {
+        XMLReader reader = XmlObjectFactory.newXMLReader();
+        reader.setContentHandler(contentHandler);
+        reader.parse(new InputSource(in));
+    }
+
+    /**
+     * Parses xml from the given input stream and fires events on the given SAX
+     * handler.
+     */
+    public static void parse(InputStream in, Encoding encoding,
+            ContentHandler contentHandler) throws IOException, SAXException {
+        XMLReader reader = XmlObjectFactory.newXMLReader();
+        reader.setContentHandler(contentHandler);
+        InputSource source = new InputSource(in);
+        source.setEncoding(encoding.expatName);
+        reader.parse(source);
+    }
+
+    /**
+     * Returns a new pull parser with namespace support.
+     */
+    public static XmlPullParser newPullParser() {
+        try {
+            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            return parser;
+        } catch (XmlPullParserException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Creates a new xml serializer.
+     */
+    public static XmlSerializer newSerializer() {
+        return XmlObjectFactory.newXmlSerializer();
+    }
+
+    /**
+     * Supported character encodings.
+     */
+    public enum Encoding {
+
+        US_ASCII("US-ASCII"),
+        UTF_8("UTF-8"),
+        UTF_16("UTF-16"),
+        ISO_8859_1("ISO-8859-1");
+
+        final String expatName;
+
+        Encoding(String expatName) {
+            this.expatName = expatName;
+        }
+    }
+
+    /**
+     * Finds an encoding by name. Returns UTF-8 if you pass {@code null}.
+     */
+    public static Encoding findEncodingByName(String encodingName)
+            throws UnsupportedEncodingException {
+        if (encodingName == null) {
+            return Encoding.UTF_8;
+        }
+
+        for (Encoding encoding : Encoding.values()) {
+            if (encoding.expatName.equalsIgnoreCase(encodingName))
+                return encoding;
+        }
+        throw new UnsupportedEncodingException(encodingName);
+    }
+
+    /**
+     * Return an AttributeSet interface for use with the given XmlPullParser.
+     * If the given parser itself implements AttributeSet, that implementation
+     * is simply returned.  Otherwise a wrapper class is
+     * instantiated on top of the XmlPullParser, as a proxy for retrieving its
+     * attributes, and returned to you.
+     *
+     * @param parser The existing parser for which you would like an
+     *               AttributeSet.
+     *
+     * @return An AttributeSet you can use to retrieve the
+     *         attribute values at each of the tags as the parser moves
+     *         through its XML document.
+     *
+     * @see AttributeSet
+     */
+    public static AttributeSet asAttributeSet(XmlPullParser parser) {
+        return (parser instanceof AttributeSet)
+                ? (AttributeSet) parser
+                : new XmlPullAttributes(parser);
+    }
+}
diff --git a/android/util/XmlPullAttributes.java b/android/util/XmlPullAttributes.java
new file mode 100644
index 0000000..d83b355
--- /dev/null
+++ b/android/util/XmlPullAttributes.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Provides an implementation of AttributeSet on top of an XmlPullParser.
+ */
+class XmlPullAttributes implements AttributeSet {
+    @UnsupportedAppUsage
+    public XmlPullAttributes(XmlPullParser parser) {
+        mParser = parser;
+    }
+
+    public int getAttributeCount() {
+        return mParser.getAttributeCount();
+    }
+
+    public String getAttributeNamespace (int index) {
+        return mParser.getAttributeNamespace(index);
+    }
+
+    public String getAttributeName(int index) {
+        return mParser.getAttributeName(index);
+    }
+
+    public String getAttributeValue(int index) {
+        return mParser.getAttributeValue(index);
+    }
+
+    public String getAttributeValue(String namespace, String name) {
+        return mParser.getAttributeValue(namespace, name);
+    }
+
+    public String getPositionDescription() {
+        return mParser.getPositionDescription();
+    }
+
+    public int getAttributeNameResource(int index) {
+        return 0;
+    }
+
+    public int getAttributeListValue(String namespace, String attribute,
+            String[] options, int defaultValue) {
+        return XmlUtils.convertValueToList(
+            getAttributeValue(namespace, attribute), options, defaultValue);
+    }
+
+    public boolean getAttributeBooleanValue(String namespace, String attribute,
+            boolean defaultValue) {
+        return XmlUtils.convertValueToBoolean(
+            getAttributeValue(namespace, attribute), defaultValue);
+    }
+
+    public int getAttributeResourceValue(String namespace, String attribute,
+            int defaultValue) {
+        return XmlUtils.convertValueToInt(
+            getAttributeValue(namespace, attribute), defaultValue);
+    }
+
+    public int getAttributeIntValue(String namespace, String attribute,
+            int defaultValue) {
+        return XmlUtils.convertValueToInt(
+            getAttributeValue(namespace, attribute), defaultValue);
+    }
+
+    public int getAttributeUnsignedIntValue(String namespace, String attribute,
+            int defaultValue) {
+        return XmlUtils.convertValueToUnsignedInt(
+            getAttributeValue(namespace, attribute), defaultValue);
+    }
+
+    public float getAttributeFloatValue(String namespace, String attribute,
+            float defaultValue) {
+        String s = getAttributeValue(namespace, attribute);
+        if (s != null) {
+            return Float.parseFloat(s);
+        }
+        return defaultValue;
+    }
+
+    public int getAttributeListValue(int index,
+            String[] options, int defaultValue) {
+        return XmlUtils.convertValueToList(
+            getAttributeValue(index), options, defaultValue);
+    }
+
+    public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+        return XmlUtils.convertValueToBoolean(
+            getAttributeValue(index), defaultValue);
+    }
+
+    public int getAttributeResourceValue(int index, int defaultValue) {
+        return XmlUtils.convertValueToInt(
+            getAttributeValue(index), defaultValue);
+    }
+
+    public int getAttributeIntValue(int index, int defaultValue) {
+        return XmlUtils.convertValueToInt(
+            getAttributeValue(index), defaultValue);
+    }
+
+    public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+        return XmlUtils.convertValueToUnsignedInt(
+            getAttributeValue(index), defaultValue);
+    }
+
+    public float getAttributeFloatValue(int index, float defaultValue) {
+        String s = getAttributeValue(index);
+        if (s != null) {
+            return Float.parseFloat(s);
+        }
+        return defaultValue;
+    }
+
+    public String getIdAttribute() {
+        return getAttributeValue(null, "id");
+    }
+
+    public String getClassAttribute() {
+        return getAttributeValue(null, "class");
+    }
+
+    public int getIdAttributeResourceValue(int defaultValue) {
+        return getAttributeResourceValue(null, "id", defaultValue);
+    }
+
+    public int getStyleAttribute() {
+        return getAttributeResourceValue(null, "style", 0);
+    }
+
+    @UnsupportedAppUsage
+    /*package*/ XmlPullParser mParser;
+}
diff --git a/android/util/Xml_Delegate.java b/android/util/Xml_Delegate.java
new file mode 100644
index 0000000..e309dc6
--- /dev/null
+++ b/android/util/Xml_Delegate.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Delegate overriding some methods of android.util.Xml
+ *
+ * Through the layoutlib_create tool, the original methods of Xml have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ */
+public class Xml_Delegate {
+    @LayoutlibDelegate
+    /*package*/ static XmlPullParser newPullParser() {
+        try {
+            return ParserFactory.create();
+        } catch (XmlPullParserException e) {
+            throw new AssertionError();
+        }
+    }
+}
diff --git a/android/util/apk/ApkSignatureSchemeV2Verifier.java b/android/util/apk/ApkSignatureSchemeV2Verifier.java
new file mode 100644
index 0000000..346fe29
--- /dev/null
+++ b/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2016 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.apk;
+
+import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * APK Signature Scheme v2 verifier.
+ *
+ * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
+ * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
+ * uncompressed contents of ZIP entries.
+ *
+ * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a>
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureSchemeV2Verifier {
+
+    /**
+     * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
+     */
+    public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
+
+    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+
+    /**
+     * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
+     *
+     * <p><b>NOTE: This method does not verify the signature.</b>
+     */
+    public static boolean hasSignature(String apkFile) throws IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            findSignature(apk);
+            return true;
+        } catch (SignatureNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
+     * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static X509Certificate[][] verify(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        VerifiedSigner vSigner = verify(apkFile, true);
+        return vSigner.certs;
+    }
+
+    /**
+     * Returns the certificates associated with each signer for the given APK without verification.
+     * This method is dangerous and should not be used, unless the caller is absolutely certain the
+     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v2
+     * Block while gathering signer information.  The APK contents are not verified.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static X509Certificate[][] unsafeGetCertsWithoutVerification(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        VerifiedSigner vSigner = verify(apkFile, false);
+        return vSigner.certs;
+    }
+
+    /**
+     * Same as above returns the full signer object, containing additional info e.g. digest.
+     */
+    public static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            return verify(apk, verifyIntegrity);
+        }
+    }
+
+    /**
+     * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
+     * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not
+     *         verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        SignatureInfo signatureInfo = findSignature(apk);
+        return verify(apk, signatureInfo, verifyIntegrity);
+    }
+
+    /**
+     * Returns the APK Signature Scheme v2 block contained in the provided APK file and the
+     * additional information relevant for verifying the block against the file.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    private static SignatureInfo findSignature(RandomAccessFile apk)
+            throws IOException, SignatureNotFoundException {
+        return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
+    }
+
+    /**
+     * Verifies the contents of the provided APK file against the provided APK Signature Scheme v2
+     * Block.
+     *
+     * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it
+     *        against the APK file.
+     */
+    private static VerifiedSigner verify(
+            RandomAccessFile apk,
+            SignatureInfo signatureInfo,
+            boolean doVerifyIntegrity) throws SecurityException, IOException {
+        int signerCount = 0;
+        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
+        List<X509Certificate[]> signerCerts = new ArrayList<>();
+        CertificateFactory certFactory;
+        try {
+            certFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+        ByteBuffer signers;
+        try {
+            signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
+        } catch (IOException e) {
+            throw new SecurityException("Failed to read list of signers", e);
+        }
+        while (signers.hasRemaining()) {
+            signerCount++;
+            try {
+                ByteBuffer signer = getLengthPrefixedSlice(signers);
+                X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory);
+                signerCerts.add(certs);
+            } catch (IOException | BufferUnderflowException | SecurityException e) {
+                throw new SecurityException(
+                        "Failed to parse/verify signer #" + signerCount + " block",
+                        e);
+            }
+        }
+
+        if (signerCount < 1) {
+            throw new SecurityException("No signers found");
+        }
+
+        if (contentDigests.isEmpty()) {
+            throw new SecurityException("No content digests found");
+        }
+
+        if (doVerifyIntegrity) {
+            ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
+        }
+
+        byte[] verityRootHash = null;
+        if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
+            byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
+            verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
+                    verityDigest, apk.length(), signatureInfo);
+        }
+
+        byte[] digest = pickBestDigestForV4(contentDigests);
+
+        return new VerifiedSigner(
+                signerCerts.toArray(new X509Certificate[signerCerts.size()][]),
+                verityRootHash, digest);
+    }
+
+    private static X509Certificate[] verifySigner(
+            ByteBuffer signerBlock,
+            Map<Integer, byte[]> contentDigests,
+            CertificateFactory certFactory) throws SecurityException, IOException {
+        ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
+        ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
+        byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
+
+        int signatureCount = 0;
+        int bestSigAlgorithm = -1;
+        byte[] bestSigAlgorithmSignatureBytes = null;
+        List<Integer> signaturesSigAlgorithms = new ArrayList<>();
+        while (signatures.hasRemaining()) {
+            signatureCount++;
+            try {
+                ByteBuffer signature = getLengthPrefixedSlice(signatures);
+                if (signature.remaining() < 8) {
+                    throw new SecurityException("Signature record too short");
+                }
+                int sigAlgorithm = signature.getInt();
+                signaturesSigAlgorithms.add(sigAlgorithm);
+                if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
+                    continue;
+                }
+                if ((bestSigAlgorithm == -1)
+                        || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
+                    bestSigAlgorithm = sigAlgorithm;
+                    bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new SecurityException(
+                        "Failed to parse signature record #" + signatureCount,
+                        e);
+            }
+        }
+        if (bestSigAlgorithm == -1) {
+            if (signatureCount == 0) {
+                throw new SecurityException("No signatures found");
+            } else {
+                throw new SecurityException("No supported signatures found");
+            }
+        }
+
+        String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
+        Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+                getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
+        String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+        AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+        boolean sigVerified;
+        try {
+            PublicKey publicKey =
+                    KeyFactory.getInstance(keyAlgorithm)
+                            .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+            Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+            sig.initVerify(publicKey);
+            if (jcaSignatureAlgorithmParams != null) {
+                sig.setParameter(jcaSignatureAlgorithmParams);
+            }
+            sig.update(signedData);
+            sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+                | InvalidAlgorithmParameterException | SignatureException e) {
+            throw new SecurityException(
+                    "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+        }
+        if (!sigVerified) {
+            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+        }
+
+        // Signature over signedData has verified.
+
+        byte[] contentDigest = null;
+        signedData.clear();
+        ByteBuffer digests = getLengthPrefixedSlice(signedData);
+        List<Integer> digestsSigAlgorithms = new ArrayList<>();
+        int digestCount = 0;
+        while (digests.hasRemaining()) {
+            digestCount++;
+            try {
+                ByteBuffer digest = getLengthPrefixedSlice(digests);
+                if (digest.remaining() < 8) {
+                    throw new IOException("Record too short");
+                }
+                int sigAlgorithm = digest.getInt();
+                digestsSigAlgorithms.add(sigAlgorithm);
+                if (sigAlgorithm == bestSigAlgorithm) {
+                    contentDigest = readLengthPrefixedByteArray(digest);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new IOException("Failed to parse digest record #" + digestCount, e);
+            }
+        }
+
+        if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
+            throw new SecurityException(
+                    "Signature algorithms don't match between digests and signatures records");
+        }
+        int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
+        byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
+        if ((previousSignerDigest != null)
+                && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
+            throw new SecurityException(
+                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                    + " contents digest does not match the digest specified by a preceding signer");
+        }
+
+        ByteBuffer certificates = getLengthPrefixedSlice(signedData);
+        List<X509Certificate> certs = new ArrayList<>();
+        int certificateCount = 0;
+        while (certificates.hasRemaining()) {
+            certificateCount++;
+            byte[] encodedCert = readLengthPrefixedByteArray(certificates);
+            X509Certificate certificate;
+            try {
+                certificate = (X509Certificate)
+                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+            } catch (CertificateException e) {
+                throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
+            }
+            certificate = new VerbatimX509Certificate(
+                    certificate, encodedCert);
+            certs.add(certificate);
+        }
+
+        if (certs.isEmpty()) {
+            throw new SecurityException("No certificates listed");
+        }
+        X509Certificate mainCertificate = certs.get(0);
+        byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+        if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+            throw new SecurityException(
+                    "Public key mismatch between certificate and signature record");
+        }
+
+        ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+        verifyAdditionalAttributes(additionalAttrs);
+
+        return certs.toArray(new X509Certificate[certs.size()]);
+    }
+
+    // Attribute to check whether a newer APK Signature Scheme signature was stripped
+    private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d;
+
+    private static void verifyAdditionalAttributes(ByteBuffer attrs)
+            throws SecurityException, IOException {
+        while (attrs.hasRemaining()) {
+            ByteBuffer attr = getLengthPrefixedSlice(attrs);
+            if (attr.remaining() < 4) {
+                throw new IOException("Remaining buffer too short to contain additional attribute "
+                        + "ID. Remaining: " + attr.remaining());
+            }
+            int id = attr.getInt();
+            switch (id) {
+                case STRIPPING_PROTECTION_ATTR_ID:
+                    if (attr.remaining() < 4) {
+                        throw new IOException("V2 Signature Scheme Stripping Protection Attribute "
+                                + " value too small.  Expected 4 bytes, but found "
+                                + attr.remaining());
+                    }
+                    int vers = attr.getInt();
+                    if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+                        throw new SecurityException("V2 signature indicates APK is signed using APK"
+                                + " Signature Scheme v3, but none was found. Signature stripped?");
+                    }
+                    break;
+                default:
+                    // not the droid we're looking for, move along, move along.
+                    break;
+            }
+        }
+        return;
+    }
+
+    static byte[] getVerityRootHash(String apkPath)
+            throws IOException, SignatureNotFoundException, SecurityException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+            SignatureInfo signatureInfo = findSignature(apk);
+            VerifiedSigner vSigner = verify(apk, false);
+            return vSigner.verityRootHash;
+        }
+    }
+
+    static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
+            throws IOException, SignatureNotFoundException, SecurityException, DigestException,
+                   NoSuchAlgorithmException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+            SignatureInfo signatureInfo = findSignature(apk);
+            return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo);
+        }
+    }
+
+    static byte[] generateApkVerityRootHash(String apkPath)
+            throws IOException, SignatureNotFoundException, DigestException,
+                   NoSuchAlgorithmException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+            SignatureInfo signatureInfo = findSignature(apk);
+            VerifiedSigner vSigner = verify(apk, false);
+            if (vSigner.verityRootHash == null) {
+                return null;
+            }
+            return VerityBuilder.generateApkVerityRootHash(
+                    apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
+        }
+    }
+
+    /**
+     * Verified APK Signature Scheme v2 signer.
+     *
+     * @hide for internal use only.
+     */
+    public static class VerifiedSigner {
+        public final X509Certificate[][] certs;
+
+        public final byte[] verityRootHash;
+        public final byte[] digest;
+
+        public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash, byte[] digest) {
+            this.certs = certs;
+            this.verityRootHash = verityRootHash;
+            this.digest = digest;
+        }
+
+    }
+}
diff --git a/android/util/apk/ApkSignatureSchemeV3Verifier.java b/android/util/apk/ApkSignatureSchemeV3Verifier.java
new file mode 100644
index 0000000..4ab541b
--- /dev/null
+++ b/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2018 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.apk;
+
+import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
+import android.os.Build;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * APK Signature Scheme v3 verifier.
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureSchemeV3Verifier {
+
+    /**
+     * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
+     */
+    public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
+
+    private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+
+    /**
+     * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
+     *
+     * <p><b>NOTE: This method does not verify the signature.</b>
+     */
+    public static boolean hasSignature(String apkFile) throws IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            findSignature(apk);
+            return true;
+        } catch (SignatureNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
+     * verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static VerifiedSigner verify(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        return verify(apkFile, true);
+    }
+
+    /**
+     * Returns the certificates associated with each signer for the given APK without verification.
+     * This method is dangerous and should not be used, unless the caller is absolutely certain the
+     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v3
+     * Block while gathering signer information.  The APK contents are not verified.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static VerifiedSigner unsafeGetCertsWithoutVerification(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        return verify(apkFile, false);
+    }
+
+    private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            return verify(apk, verifyIntegrity);
+        }
+    }
+
+    /**
+     * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
+     *         verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        SignatureInfo signatureInfo = findSignature(apk);
+        return verify(apk, signatureInfo, verifyIntegrity);
+    }
+
+    /**
+     * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
+     * additional information relevant for verifying the block against the file.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    private static SignatureInfo findSignature(RandomAccessFile apk)
+            throws IOException, SignatureNotFoundException {
+        return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
+    }
+
+    /**
+     * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
+     * Block.
+     *
+     * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
+     *        against the APK file.
+     */
+    private static VerifiedSigner verify(
+            RandomAccessFile apk,
+            SignatureInfo signatureInfo,
+            boolean doVerifyIntegrity) throws SecurityException, IOException {
+        int signerCount = 0;
+        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
+        VerifiedSigner result = null;
+        CertificateFactory certFactory;
+        try {
+            certFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+        ByteBuffer signers;
+        try {
+            signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
+        } catch (IOException e) {
+            throw new SecurityException("Failed to read list of signers", e);
+        }
+        while (signers.hasRemaining()) {
+            try {
+                ByteBuffer signer = getLengthPrefixedSlice(signers);
+                result = verifySigner(signer, contentDigests, certFactory);
+                signerCount++;
+            } catch (PlatformNotSupportedException e) {
+                // this signer is for a different platform, ignore it.
+                continue;
+            } catch (IOException | BufferUnderflowException | SecurityException e) {
+                throw new SecurityException(
+                        "Failed to parse/verify signer #" + signerCount + " block",
+                        e);
+            }
+        }
+
+        if (signerCount < 1 || result == null) {
+            throw new SecurityException("No signers found");
+        }
+
+        if (signerCount != 1) {
+            throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
+                    + "multiple signers found.");
+        }
+
+        if (contentDigests.isEmpty()) {
+            throw new SecurityException("No content digests found");
+        }
+
+        if (doVerifyIntegrity) {
+            ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
+        }
+
+        if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
+            byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
+            result.verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
+                    verityDigest, apk.length(), signatureInfo);
+        }
+
+        result.digest = pickBestDigestForV4(contentDigests);
+
+        return result;
+    }
+
+    private static VerifiedSigner verifySigner(
+            ByteBuffer signerBlock,
+            Map<Integer, byte[]> contentDigests,
+            CertificateFactory certFactory)
+            throws SecurityException, IOException, PlatformNotSupportedException {
+        ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
+        int minSdkVersion = signerBlock.getInt();
+        int maxSdkVersion = signerBlock.getInt();
+
+        if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
+            // this signature isn't meant to be used with this platform, skip it.
+            throw new PlatformNotSupportedException(
+                    "Signer not supported by this platform "
+                    + "version. This platform: " + Build.VERSION.SDK_INT
+                    + ", signer minSdkVersion: " + minSdkVersion
+                    + ", maxSdkVersion: " + maxSdkVersion);
+        }
+
+        ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
+        byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
+
+        int signatureCount = 0;
+        int bestSigAlgorithm = -1;
+        byte[] bestSigAlgorithmSignatureBytes = null;
+        List<Integer> signaturesSigAlgorithms = new ArrayList<>();
+        while (signatures.hasRemaining()) {
+            signatureCount++;
+            try {
+                ByteBuffer signature = getLengthPrefixedSlice(signatures);
+                if (signature.remaining() < 8) {
+                    throw new SecurityException("Signature record too short");
+                }
+                int sigAlgorithm = signature.getInt();
+                signaturesSigAlgorithms.add(sigAlgorithm);
+                if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
+                    continue;
+                }
+                if ((bestSigAlgorithm == -1)
+                        || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
+                    bestSigAlgorithm = sigAlgorithm;
+                    bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new SecurityException(
+                        "Failed to parse signature record #" + signatureCount,
+                        e);
+            }
+        }
+        if (bestSigAlgorithm == -1) {
+            if (signatureCount == 0) {
+                throw new SecurityException("No signatures found");
+            } else {
+                throw new SecurityException("No supported signatures found");
+            }
+        }
+
+        String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
+        Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+                getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
+        String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+        AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+        boolean sigVerified;
+        try {
+            PublicKey publicKey =
+                    KeyFactory.getInstance(keyAlgorithm)
+                            .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+            Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+            sig.initVerify(publicKey);
+            if (jcaSignatureAlgorithmParams != null) {
+                sig.setParameter(jcaSignatureAlgorithmParams);
+            }
+            sig.update(signedData);
+            sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+                | InvalidAlgorithmParameterException | SignatureException e) {
+            throw new SecurityException(
+                    "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+        }
+        if (!sigVerified) {
+            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+        }
+
+        // Signature over signedData has verified.
+
+        byte[] contentDigest = null;
+        signedData.clear();
+        ByteBuffer digests = getLengthPrefixedSlice(signedData);
+        List<Integer> digestsSigAlgorithms = new ArrayList<>();
+        int digestCount = 0;
+        while (digests.hasRemaining()) {
+            digestCount++;
+            try {
+                ByteBuffer digest = getLengthPrefixedSlice(digests);
+                if (digest.remaining() < 8) {
+                    throw new IOException("Record too short");
+                }
+                int sigAlgorithm = digest.getInt();
+                digestsSigAlgorithms.add(sigAlgorithm);
+                if (sigAlgorithm == bestSigAlgorithm) {
+                    contentDigest = readLengthPrefixedByteArray(digest);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new IOException("Failed to parse digest record #" + digestCount, e);
+            }
+        }
+
+        if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
+            throw new SecurityException(
+                    "Signature algorithms don't match between digests and signatures records");
+        }
+        int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
+        byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
+        if ((previousSignerDigest != null)
+                && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
+            throw new SecurityException(
+                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                    + " contents digest does not match the digest specified by a preceding signer");
+        }
+
+        ByteBuffer certificates = getLengthPrefixedSlice(signedData);
+        List<X509Certificate> certs = new ArrayList<>();
+        int certificateCount = 0;
+        while (certificates.hasRemaining()) {
+            certificateCount++;
+            byte[] encodedCert = readLengthPrefixedByteArray(certificates);
+            X509Certificate certificate;
+            try {
+                certificate = (X509Certificate)
+                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+            } catch (CertificateException e) {
+                throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
+            }
+            certificate = new VerbatimX509Certificate(
+                    certificate, encodedCert);
+            certs.add(certificate);
+        }
+
+        if (certs.isEmpty()) {
+            throw new SecurityException("No certificates listed");
+        }
+        X509Certificate mainCertificate = certs.get(0);
+        byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+        if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+            throw new SecurityException(
+                    "Public key mismatch between certificate and signature record");
+        }
+
+        int signedMinSDK = signedData.getInt();
+        if (signedMinSDK != minSdkVersion) {
+            throw new SecurityException(
+                    "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
+        }
+
+        int signedMaxSDK = signedData.getInt();
+        if (signedMaxSDK != maxSdkVersion) {
+            throw new SecurityException(
+                    "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
+        }
+
+        ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+        return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
+    }
+
+    private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
+
+    private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
+            List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
+        X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
+        VerifiedProofOfRotation por = null;
+
+        while (attrs.hasRemaining()) {
+            ByteBuffer attr = getLengthPrefixedSlice(attrs);
+            if (attr.remaining() < 4) {
+                throw new IOException("Remaining buffer too short to contain additional attribute "
+                        + "ID. Remaining: " + attr.remaining());
+            }
+            int id = attr.getInt();
+            switch(id) {
+                case PROOF_OF_ROTATION_ATTR_ID:
+                    if (por != null) {
+                        throw new SecurityException("Encountered multiple Proof-of-rotation records"
+                                + " when verifying APK Signature Scheme v3 signature");
+                    }
+                    por = verifyProofOfRotationStruct(attr, certFactory);
+                    // make sure that the last certificate in the Proof-of-rotation record matches
+                    // the one used to sign this APK.
+                    try {
+                        if (por.certs.size() > 0
+                                && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
+                                        certChain[0].getEncoded())) {
+                            throw new SecurityException("Terminal certificate in Proof-of-rotation"
+                                    + " record does not match APK signing certificate");
+                        }
+                    } catch (CertificateEncodingException e) {
+                        throw new SecurityException("Failed to encode certificate when comparing"
+                                + " Proof-of-rotation record and signing certificate", e);
+                    }
+
+                    break;
+                default:
+                    // not the droid we're looking for, move along, move along.
+                    break;
+            }
+        }
+        return new VerifiedSigner(certChain, por);
+    }
+
+    private static VerifiedProofOfRotation verifyProofOfRotationStruct(
+            ByteBuffer porBuf,
+            CertificateFactory certFactory)
+            throws SecurityException, IOException {
+        int levelCount = 0;
+        int lastSigAlgorithm = -1;
+        X509Certificate lastCert = null;
+        List<X509Certificate> certs = new ArrayList<>();
+        List<Integer> flagsList = new ArrayList<>();
+
+        // Proof-of-rotation struct:
+        // A uint32 version code followed by basically a singly linked list of nodes, called levels
+        // here, each of which have the following structure:
+        // * length-prefix for the entire level
+        //     - length-prefixed signed data (if previous level exists)
+        //         * length-prefixed X509 Certificate
+        //         * uint32 signature algorithm ID describing how this signed data was signed
+        //     - uint32 flags describing how to treat the cert contained in this level
+        //     - uint32 signature algorithm ID to use to verify the signature of the next level. The
+        //         algorithm here must match the one in the signed data section of the next level.
+        //     - length-prefixed signature over the signed data in this level.  The signature here
+        //         is verified using the certificate from the previous level.
+        // The linking is provided by the certificate of each level signing the one of the next.
+
+        try {
+
+            // get the version code, but don't do anything with it: creator knew about all our flags
+            porBuf.getInt();
+            HashSet<X509Certificate> certHistorySet = new HashSet<>();
+            while (porBuf.hasRemaining()) {
+                levelCount++;
+                ByteBuffer level = getLengthPrefixedSlice(porBuf);
+                ByteBuffer signedData = getLengthPrefixedSlice(level);
+                int flags = level.getInt();
+                int sigAlgorithm = level.getInt();
+                byte[] signature = readLengthPrefixedByteArray(level);
+
+                if (lastCert != null) {
+                    // Use previous level cert to verify current level
+                    Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
+                            getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
+                    PublicKey publicKey = lastCert.getPublicKey();
+                    Signature sig = Signature.getInstance(sigAlgParams.first);
+                    sig.initVerify(publicKey);
+                    if (sigAlgParams.second != null) {
+                        sig.setParameter(sigAlgParams.second);
+                    }
+                    sig.update(signedData);
+                    if (!sig.verify(signature)) {
+                        throw new SecurityException("Unable to verify signature of certificate #"
+                                + levelCount + " using " + sigAlgParams.first + " when verifying"
+                                + " Proof-of-rotation record");
+                    }
+                }
+
+                signedData.rewind();
+                byte[] encodedCert = readLengthPrefixedByteArray(signedData);
+                int signedSigAlgorithm = signedData.getInt();
+                if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
+                    throw new SecurityException("Signing algorithm ID mismatch for certificate #"
+                            + levelCount + " when verifying Proof-of-rotation record");
+                }
+                lastCert = (X509Certificate)
+                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+                lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
+
+                lastSigAlgorithm = sigAlgorithm;
+                if (certHistorySet.contains(lastCert)) {
+                    throw new SecurityException("Encountered duplicate entries in "
+                            + "Proof-of-rotation record at certificate #" + levelCount + ".  All "
+                            + "signing certificates should be unique");
+                }
+                certHistorySet.add(lastCert);
+                certs.add(lastCert);
+                flagsList.add(flags);
+            }
+        } catch (IOException | BufferUnderflowException e) {
+            throw new IOException("Failed to parse Proof-of-rotation record", e);
+        } catch (NoSuchAlgorithmException | InvalidKeyException
+                | InvalidAlgorithmParameterException | SignatureException e) {
+            throw new SecurityException(
+                    "Failed to verify signature over signed data for certificate #"
+                            + levelCount + " when verifying Proof-of-rotation record", e);
+        } catch (CertificateException e) {
+            throw new SecurityException("Failed to decode certificate #" + levelCount
+                    + " when verifying Proof-of-rotation record", e);
+        }
+        return new VerifiedProofOfRotation(certs, flagsList);
+    }
+
+    static byte[] getVerityRootHash(String apkPath)
+            throws IOException, SignatureNotFoundException, SecurityException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+            SignatureInfo signatureInfo = findSignature(apk);
+            VerifiedSigner vSigner = verify(apk, false);
+            return vSigner.verityRootHash;
+        }
+    }
+
+    static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
+            throws IOException, SignatureNotFoundException, SecurityException, DigestException,
+                   NoSuchAlgorithmException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+            SignatureInfo signatureInfo = findSignature(apk);
+            return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo);
+        }
+    }
+
+    static byte[] generateApkVerityRootHash(String apkPath)
+            throws NoSuchAlgorithmException, DigestException, IOException,
+                   SignatureNotFoundException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+            SignatureInfo signatureInfo = findSignature(apk);
+            VerifiedSigner vSigner = verify(apk, false);
+            if (vSigner.verityRootHash == null) {
+                return null;
+            }
+            return VerityBuilder.generateApkVerityRootHash(
+                    apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
+        }
+    }
+
+    /**
+     * Verified processed proof of rotation.
+     *
+     * @hide for internal use only.
+     */
+    public static class VerifiedProofOfRotation {
+        public final List<X509Certificate> certs;
+        public final List<Integer> flagsList;
+
+        public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
+            this.certs = certs;
+            this.flagsList = flagsList;
+        }
+    }
+
+    /**
+     * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
+     *
+     * @hide for internal use only.
+     */
+    public static class VerifiedSigner {
+        public final X509Certificate[] certs;
+        public final VerifiedProofOfRotation por;
+
+        public byte[] verityRootHash;
+        public byte[] digest;
+
+        public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
+            this.certs = certs;
+            this.por = por;
+        }
+
+    }
+
+    private static class PlatformNotSupportedException extends Exception {
+
+        PlatformNotSupportedException(String s) {
+            super(s);
+        }
+    }
+}
diff --git a/android/util/apk/ApkSignatureSchemeV4Verifier.java b/android/util/apk/ApkSignatureSchemeV4Verifier.java
new file mode 100644
index 0000000..d40efce
--- /dev/null
+++ b/android/util/apk/ApkSignatureSchemeV4Verifier.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 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.apk;
+
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
+
+import android.os.incremental.IncrementalManager;
+import android.os.incremental.V4Signature;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+
+/**
+ * APK Signature Scheme v4 verifier.
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureSchemeV4Verifier {
+    /**
+     * Extracts and verifies APK Signature Scheme v4 signatures of the provided APK and returns the
+     * certificates associated with each signer.
+     */
+    public static VerifiedSigner extractCertificates(String apkFile)
+            throws SignatureNotFoundException, SecurityException {
+        final File apk = new File(apkFile);
+        final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature(
+                apk.getAbsolutePath());
+        if (signatureBytes == null || signatureBytes.length == 0) {
+            throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS.");
+        }
+
+        final V4Signature signature;
+        final V4Signature.HashingInfo hashingInfo;
+        final V4Signature.SigningInfo signingInfo;
+        try {
+            signature = V4Signature.readFrom(signatureBytes);
+
+            if (!signature.isVersionSupported()) {
+                throw new SecurityException(
+                        "v4 signature version " + signature.version + " is not supported");
+            }
+
+            hashingInfo = V4Signature.HashingInfo.fromByteArray(signature.hashingInfo);
+            signingInfo = V4Signature.SigningInfo.fromByteArray(signature.signingInfo);
+        } catch (IOException e) {
+            throw new SignatureNotFoundException("Failed to read V4 signature.", e);
+        }
+
+        final byte[] signedData = V4Signature.getSigningData(apk.length(), hashingInfo,
+                signingInfo);
+
+        return verifySigner(signingInfo, signedData);
+    }
+
+    private static VerifiedSigner verifySigner(V4Signature.SigningInfo signingInfo,
+            final byte[] signedData) throws SecurityException {
+        if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) {
+            throw new SecurityException("No supported signatures found");
+        }
+
+        final int signatureAlgorithmId = signingInfo.signatureAlgorithmId;
+        final byte[] signatureBytes = signingInfo.signature;
+        final byte[] publicKeyBytes = signingInfo.publicKey;
+        final byte[] encodedCert = signingInfo.certificate;
+
+        String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(signatureAlgorithmId);
+        Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+                getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithmId);
+        String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+        AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+        boolean sigVerified;
+        try {
+            PublicKey publicKey =
+                    KeyFactory.getInstance(keyAlgorithm)
+                            .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+            Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+            sig.initVerify(publicKey);
+            if (jcaSignatureAlgorithmParams != null) {
+                sig.setParameter(jcaSignatureAlgorithmParams);
+            }
+            sig.update(signedData);
+            sigVerified = sig.verify(signatureBytes);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+                | InvalidAlgorithmParameterException | SignatureException e) {
+            throw new SecurityException(
+                    "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+        }
+        if (!sigVerified) {
+            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+        }
+
+        // Signature over signedData has verified.
+        CertificateFactory certFactory;
+        try {
+            certFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+
+        X509Certificate certificate;
+        try {
+            certificate = (X509Certificate)
+                    certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+        } catch (CertificateException e) {
+            throw new SecurityException("Failed to decode certificate", e);
+        }
+        certificate = new VerbatimX509Certificate(certificate, encodedCert);
+
+        byte[] certificatePublicKeyBytes = certificate.getPublicKey().getEncoded();
+        if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+            throw new SecurityException(
+                    "Public key mismatch between certificate and signature record");
+        }
+
+        return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.apkDigest);
+    }
+
+    /**
+     * Verified APK Signature Scheme v4 signer, including V3 digest.
+     *
+     * @hide for internal use only.
+     */
+    public static class VerifiedSigner {
+        public final Certificate[] certs;
+        public byte[] apkDigest;
+
+        public VerifiedSigner(Certificate[] certs, byte[] apkDigest) {
+            this.certs = certs;
+            this.apkDigest = apkDigest;
+        }
+
+    }
+}
diff --git a/android/util/apk/ApkSignatureVerifier.java b/android/util/apk/ApkSignatureVerifier.java
new file mode 100644
index 0000000..ab8f80d
--- /dev/null
+++ b/android/util/apk/ApkSignatureVerifier.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2017 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.apk;
+
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Trace;
+import android.util.jar.StrictJarFile;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.DigestException;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.ZipEntry;
+
+/**
+ * Facade class that takes care of the details of APK verification on
+ * behalf of PackageParser.
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureVerifier {
+
+    private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
+
+    /**
+     * Verifies the provided APK and returns the certificates associated with each signer.
+     *
+     * @throws PackageParserException if the APK's signature failed to verify.
+     */
+    public static PackageParser.SigningDetails verify(String apkPath,
+            @SignatureSchemeVersion int minSignatureSchemeVersion)
+            throws PackageParserException {
+        return verifySignatures(apkPath, minSignatureSchemeVersion, true);
+    }
+
+    /**
+     * Returns the certificates associated with each signer for the given APK without verification.
+     * This method is dangerous and should not be used, unless the caller is absolutely certain the
+     * APK is trusted.
+     *
+     * @throws PackageParserException if there was a problem collecting certificates.
+     */
+    public static PackageParser.SigningDetails unsafeGetCertsWithoutVerification(
+            String apkPath, int minSignatureSchemeVersion)
+            throws PackageParserException {
+        return verifySignatures(apkPath, minSignatureSchemeVersion, false);
+    }
+
+    /**
+     * Verifies the provided APK using all allowed signing schemas.
+     * @return the certificates associated with each signer.
+     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     * @throws PackageParserException if there was a problem collecting certificates
+     */
+    private static PackageParser.SigningDetails verifySignatures(String apkPath,
+            @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
+            throws PackageParserException {
+
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
+            // V3 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
+        // first try v4
+        try {
+            return verifyV4Signature(apkPath, minSignatureSchemeVersion, verifyFull);
+        } catch (SignatureNotFoundException e) {
+            // not signed with v4, try older if allowed
+            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No APK Signature Scheme v4 signature in package " + apkPath, e);
+            }
+        }
+
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
+            // V3 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
+        return verifyV3AndBelowSignatures(apkPath, minSignatureSchemeVersion, verifyFull);
+    }
+
+    private static PackageParser.SigningDetails verifyV3AndBelowSignatures(String apkPath,
+            @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
+            throws PackageParserException {
+        // try v3
+        try {
+            return verifyV3Signature(apkPath, verifyFull);
+        } catch (SignatureNotFoundException e) {
+            // not signed with v3, try older if allowed
+            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No APK Signature Scheme v3 signature in package " + apkPath, e);
+            }
+        }
+
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
+            // V2 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
+        // try v2
+        try {
+            return verifyV2Signature(apkPath, verifyFull);
+        } catch (SignatureNotFoundException e) {
+            // not signed with v2, try older if allowed
+            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No APK Signature Scheme v2 signature in package " + apkPath, e);
+            }
+        }
+
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
+            // V1 and is older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
+        // v2 didn't work, try jarsigner
+        return verifyV1Signature(apkPath, verifyFull);
+    }
+
+    /**
+     * Verifies the provided APK using V4 schema.
+     *
+     * @param verifyFull whether to verify (V4 vs V3) or just collect certificates.
+     * @return the certificates associated with each signer.
+     * @throws SignatureNotFoundException if there are no V4 signatures in the APK
+     * @throws PackageParserException     if there was a problem collecting certificates
+     */
+    private static PackageParser.SigningDetails verifyV4Signature(String apkPath,
+            @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
+            throws SignatureNotFoundException, PackageParserException {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
+        try {
+            ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
+                    ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
+            Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+
+            if (verifyFull) {
+                byte[] nonstreamingDigest = null;
+                Certificate[][] nonstreamingCerts = null;
+
+                try {
+                    // v4 is an add-on and requires v2 or v3 signature to validate against its
+                    // certificate and digest
+                    ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer =
+                            ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
+                    nonstreamingDigest = v3Signer.digest;
+                    nonstreamingCerts = new Certificate[][]{v3Signer.certs};
+                } catch (SignatureNotFoundException e) {
+                    try {
+                        ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
+                                ApkSignatureSchemeV2Verifier.verify(apkPath, false);
+                        nonstreamingDigest = v2Signer.digest;
+                        nonstreamingCerts = v2Signer.certs;
+                    } catch (SignatureNotFoundException ee) {
+                        throw new SecurityException(
+                                "V4 verification failed to collect V2/V3 certificates from : "
+                                        + apkPath, ee);
+                    }
+                }
+
+                Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
+                if (nonstreamingSigs.length != signerSigs.length) {
+                    throw new SecurityException(
+                            "Invalid number of certificates: " + nonstreamingSigs.length);
+                }
+
+                for (int i = 0, size = signerSigs.length; i < size; ++i) {
+                    if (!nonstreamingSigs[i].equals(signerSigs[i])) {
+                        throw new SecurityException(
+                                "V4 signature certificate does not match V2/V3");
+                    }
+                }
+
+                if (!ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest,
+                        vSigner.apkDigest.length)) {
+                    throw new SecurityException("APK digest in V4 signature does not match V2/V3");
+                }
+            }
+
+            return new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V4);
+        } catch (SignatureNotFoundException e) {
+            throw e;
+        } catch (Exception e) {
+            // APK Signature Scheme v4 signature found but did not verify
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v4", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    /**
+     * Verifies the provided APK using V3 schema.
+     *
+     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     * @return the certificates associated with each signer.
+     * @throws SignatureNotFoundException if there are no V3 signatures in the APK
+     * @throws PackageParserException     if there was a problem collecting certificates
+     */
+    private static PackageParser.SigningDetails verifyV3Signature(String apkPath,
+            boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3");
+        try {
+            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+                    verifyFull ? ApkSignatureSchemeV3Verifier.verify(apkPath)
+                            : ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(
+                                    apkPath);
+            Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+            Signature[] pastSignerSigs = null;
+            if (vSigner.por != null) {
+                // populate proof-of-rotation information
+                pastSignerSigs = new Signature[vSigner.por.certs.size()];
+                for (int i = 0; i < pastSignerSigs.length; i++) {
+                    pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
+                    pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
+                }
+            }
+            return new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs);
+        } catch (SignatureNotFoundException e) {
+            throw e;
+        } catch (Exception e) {
+            // APK Signature Scheme v3 signature found but did not verify
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v3", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    /**
+     * Verifies the provided APK using V2 schema.
+     *
+     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     * @return the certificates associated with each signer.
+     * @throws SignatureNotFoundException if there are no V2 signatures in the APK
+     * @throws PackageParserException     if there was a problem collecting certificates
+     */
+    private static PackageParser.SigningDetails verifyV2Signature(String apkPath,
+            boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2");
+        try {
+            Certificate[][] signerCerts = verifyFull ? ApkSignatureSchemeV2Verifier.verify(apkPath)
+                    : ApkSignatureSchemeV2Verifier.unsafeGetCertsWithoutVerification(apkPath);
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+            return new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V2);
+        } catch (SignatureNotFoundException e) {
+            throw e;
+        } catch (Exception e) {
+            // APK Signature Scheme v2 signature found but did not verify
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v2", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    /**
+     * Verifies the provided APK using JAR schema.
+     * @return the certificates associated with each signer.
+     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     * @throws PackageParserException if there was a problem collecting certificates
+     */
+    private static PackageParser.SigningDetails verifyV1Signature(
+            String apkPath, boolean verifyFull)
+            throws PackageParserException {
+        StrictJarFile jarFile = null;
+
+        try {
+            final Certificate[][] lastCerts;
+            final Signature[] lastSigs;
+
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
+
+            // we still pass verify = true to ctor to collect certs, even though we're not checking
+            // the whole jar.
+            jarFile = new StrictJarFile(
+                    apkPath,
+                    true, // collect certs
+                    verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819)
+            final List<ZipEntry> toVerify = new ArrayList<>();
+
+            // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
+            // to not need to verify the whole APK when verifyFUll == false.
+            final ZipEntry manifestEntry = jarFile.findEntry(
+                    PackageParser.ANDROID_MANIFEST_FILENAME);
+            if (manifestEntry == null) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                        "Package " + apkPath + " has no manifest");
+            }
+            lastCerts = loadCertificates(jarFile, manifestEntry);
+            if (ArrayUtils.isEmpty(lastCerts)) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package "
+                        + apkPath + " has no certificates at entry "
+                        + PackageParser.ANDROID_MANIFEST_FILENAME);
+            }
+            lastSigs = convertToSignatures(lastCerts);
+
+            // fully verify all contents, except for AndroidManifest.xml  and the META-INF/ files.
+            if (verifyFull) {
+                final Iterator<ZipEntry> i = jarFile.iterator();
+                while (i.hasNext()) {
+                    final ZipEntry entry = i.next();
+                    if (entry.isDirectory()) continue;
+
+                    final String entryName = entry.getName();
+                    if (entryName.startsWith("META-INF/")) continue;
+                    if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue;
+
+                    toVerify.add(entry);
+                }
+
+                for (ZipEntry entry : toVerify) {
+                    final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
+                    if (ArrayUtils.isEmpty(entryCerts)) {
+                        throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                                "Package " + apkPath + " has no certificates at entry "
+                                        + entry.getName());
+                    }
+
+                    // make sure all entries use the same signing certs
+                    final Signature[] entrySigs = convertToSignatures(entryCerts);
+                    if (!Signature.areExactMatch(lastSigs, entrySigs)) {
+                        throw new PackageParserException(
+                                INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                                "Package " + apkPath + " has mismatched certificates at entry "
+                                        + entry.getName());
+                    }
+                }
+            }
+            return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR);
+        } catch (GeneralSecurityException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
+                    "Failed to collect certificates from " + apkPath, e);
+        } catch (IOException | RuntimeException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath, e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            closeQuietly(jarFile);
+        }
+    }
+
+    private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry)
+            throws PackageParserException {
+        InputStream is = null;
+        try {
+            // We must read the stream for the JarEntry to retrieve
+            // its certificates.
+            is = jarFile.getInputStream(entry);
+            readFullyIgnoringContents(is);
+            return jarFile.getCertificateChains(entry);
+        } catch (IOException | RuntimeException e) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+                    "Failed reading " + entry.getName() + " in " + jarFile, e);
+        } finally {
+            IoUtils.closeQuietly(is);
+        }
+    }
+
+    private static void readFullyIgnoringContents(InputStream in) throws IOException {
+        byte[] buffer = sBuffer.getAndSet(null);
+        if (buffer == null) {
+            buffer = new byte[4096];
+        }
+
+        int n = 0;
+        int count = 0;
+        while ((n = in.read(buffer, 0, buffer.length)) != -1) {
+            count += n;
+        }
+
+        sBuffer.set(buffer);
+        return;
+    }
+
+    /**
+     * Converts an array of certificate chains into the {@code Signature} equivalent used by the
+     * PackageManager.
+     *
+     * @throws CertificateEncodingException if it is unable to create a Signature object.
+     */
+    private static Signature[] convertToSignatures(Certificate[][] certs)
+            throws CertificateEncodingException {
+        final Signature[] res = new Signature[certs.length];
+        for (int i = 0; i < certs.length; i++) {
+            res[i] = new Signature(certs[i]);
+        }
+        return res;
+    }
+
+    private static void closeQuietly(StrictJarFile jarFile) {
+        if (jarFile != null) {
+            try {
+                jarFile.close();
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    /**
+     * Returns the minimum signature scheme version required for an app targeting the specified
+     * {@code targetSdk}.
+     */
+    public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) {
+        if (targetSdk >= Build.VERSION_CODES.R) {
+            return SignatureSchemeVersion.SIGNING_BLOCK_V2;
+        }
+        return SignatureSchemeVersion.JAR;
+    }
+
+    /**
+     * Result of a successful APK verification operation.
+     */
+    public static class Result {
+        public final Certificate[][] certs;
+        public final Signature[] sigs;
+        public final int signatureSchemeVersion;
+
+        public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
+            this.certs = certs;
+            this.sigs = sigs;
+            this.signatureSchemeVersion = signingVersion;
+        }
+    }
+
+    /**
+     * @return the verity root hash in the Signing Block.
+     */
+    public static byte[] getVerityRootHash(String apkPath) throws IOException, SecurityException {
+        // first try v3
+        try {
+            return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath);
+        } catch (SignatureNotFoundException e) {
+            // try older version
+        }
+        try {
+            return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath);
+        } catch (SignatureNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code
+     * ByteBufferFactory}.
+     *
+     * @return the verity root hash of the generated Merkle tree.
+     */
+    public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
+            throws IOException, SignatureNotFoundException, SecurityException, DigestException,
+            NoSuchAlgorithmException {
+        // first try v3
+        try {
+            return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory);
+        } catch (SignatureNotFoundException e) {
+            // try older version
+        }
+        return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory);
+    }
+
+    /**
+     * Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash
+     * in Signing Block.
+     *
+     * @return FSverity root hash
+     */
+    public static byte[] generateApkVerityRootHash(String apkPath)
+            throws NoSuchAlgorithmException, DigestException, IOException {
+        // first try v3
+        try {
+            return ApkSignatureSchemeV3Verifier.generateApkVerityRootHash(apkPath);
+        } catch (SignatureNotFoundException e) {
+            // try older version
+        }
+        try {
+            return ApkSignatureSchemeV2Verifier.generateApkVerityRootHash(apkPath);
+        } catch (SignatureNotFoundException e) {
+            return null;
+        }
+    }
+}
diff --git a/android/util/apk/ApkSigningBlockUtils.java b/android/util/apk/ApkSigningBlockUtils.java
new file mode 100644
index 0000000..2a4b65d
--- /dev/null
+++ b/android/util/apk/ApkSigningBlockUtils.java
@@ -0,0 +1,818 @@
+/*
+ * Copyright (C) 2018 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.apk;
+
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Utility class for an APK Signature Scheme using the APK Signing Block.
+ *
+ * @hide for internal use only.
+ */
+final class ApkSigningBlockUtils {
+
+    private ApkSigningBlockUtils() {
+    }
+
+    /**
+     * Returns the APK Signature Scheme block contained in the provided APK file and the
+     * additional information relevant for verifying the block against the file.
+     *
+     * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs
+     *                identifying the appropriate block to find, e.g. the APK Signature Scheme v2
+     *                block ID.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using this scheme.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
+            throws IOException, SignatureNotFoundException {
+        // Find the ZIP End of Central Directory (EoCD) record.
+        Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
+        ByteBuffer eocd = eocdAndOffsetInFile.first;
+        long eocdOffset = eocdAndOffsetInFile.second;
+        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
+            throw new SignatureNotFoundException("ZIP64 APK not supported");
+        }
+
+        // Find the APK Signing Block. The block immediately precedes the Central Directory.
+        long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
+        Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
+                findApkSigningBlock(apk, centralDirOffset);
+        ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
+        long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
+
+        // Find the APK Signature Scheme Block inside the APK Signing Block.
+        ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
+                blockId);
+
+        return new SignatureInfo(
+                apkSignatureSchemeBlock,
+                apkSigningBlockOffset,
+                centralDirOffset,
+                eocdOffset,
+                eocd);
+    }
+
+    static void verifyIntegrity(
+            Map<Integer, byte[]> expectedDigests,
+            RandomAccessFile apk,
+            SignatureInfo signatureInfo) throws SecurityException {
+        if (expectedDigests.isEmpty()) {
+            throw new SecurityException("No digests provided");
+        }
+
+        boolean neverVerified = true;
+
+        Map<Integer, byte[]> expected1MbChunkDigests = new ArrayMap<>();
+        if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) {
+            expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA256,
+                    expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA256));
+        }
+        if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) {
+            expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA512,
+                    expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA512));
+        }
+        if (!expected1MbChunkDigests.isEmpty()) {
+            try {
+                verifyIntegrityFor1MbChunkBasedAlgorithm(expected1MbChunkDigests, apk.getFD(),
+                        signatureInfo);
+                neverVerified = false;
+            } catch (IOException e) {
+                throw new SecurityException("Cannot get FD", e);
+            }
+        }
+
+        if (expectedDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
+            verifyIntegrityForVerityBasedAlgorithm(
+                    expectedDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256), apk, signatureInfo);
+            neverVerified = false;
+        }
+
+        if (neverVerified) {
+            throw new SecurityException("No known digest exists for integrity check");
+        }
+    }
+
+    static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+            case SIGNATURE_DSA_WITH_SHA256:
+            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+            case SIGNATURE_VERITY_DSA_WITH_SHA256:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static void verifyIntegrityFor1MbChunkBasedAlgorithm(
+            Map<Integer, byte[]> expectedDigests,
+            FileDescriptor apkFileDescriptor,
+            SignatureInfo signatureInfo) throws SecurityException {
+        // We need to verify the integrity of the following three sections of the file:
+        // 1. Everything up to the start of the APK Signing Block.
+        // 2. ZIP Central Directory.
+        // 3. ZIP End of Central Directory (EoCD).
+        // Each of these sections is represented as a separate DataSource instance below.
+
+        // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
+        // avoid wasting physical memory. In most APK verification scenarios, the contents of the
+        // APK are already there in the OS's page cache and thus mmap does not use additional
+        // physical memory.
+        DataSource beforeApkSigningBlock =
+                new MemoryMappedFileDataSource(apkFileDescriptor, 0,
+                        signatureInfo.apkSigningBlockOffset);
+        DataSource centralDir =
+                new MemoryMappedFileDataSource(
+                        apkFileDescriptor, signatureInfo.centralDirOffset,
+                        signatureInfo.eocdOffset - signatureInfo.centralDirOffset);
+
+        // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
+        // Central Directory must be considered to point to the offset of the APK Signing Block.
+        ByteBuffer eocdBuf = signatureInfo.eocd.duplicate();
+        eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
+        ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, signatureInfo.apkSigningBlockOffset);
+        DataSource eocd = new ByteBufferDataSource(eocdBuf);
+
+        int[] digestAlgorithms = new int[expectedDigests.size()];
+        int digestAlgorithmCount = 0;
+        for (int digestAlgorithm : expectedDigests.keySet()) {
+            digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
+            digestAlgorithmCount++;
+        }
+        byte[][] actualDigests;
+        try {
+            actualDigests =
+                    computeContentDigestsPer1MbChunk(
+                            digestAlgorithms,
+                            new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
+        } catch (DigestException e) {
+            throw new SecurityException("Failed to compute digest(s) of contents", e);
+        }
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
+            byte[] actualDigest = actualDigests[i];
+            if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
+                throw new SecurityException(
+                        getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                                + " digest of contents did not verify");
+            }
+        }
+    }
+
+    private static byte[][] computeContentDigestsPer1MbChunk(
+            int[] digestAlgorithms,
+            DataSource[] contents) throws DigestException {
+        // For each digest algorithm the result is computed as follows:
+        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
+        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
+        //    No chunks are produced for empty (zero length) segments.
+        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
+        //    length in bytes (uint32 little-endian) and the chunk's contents.
+        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
+        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
+        //    segments in-order.
+
+        long totalChunkCountLong = 0;
+        for (DataSource input : contents) {
+            totalChunkCountLong += getChunkCount(input.size());
+        }
+        if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
+            throw new DigestException("Too many chunks: " + totalChunkCountLong);
+        }
+        int totalChunkCount = (int) totalChunkCountLong;
+
+        byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+            byte[] concatenationOfChunkCountAndChunkDigests =
+                    new byte[5 + totalChunkCount * digestOutputSizeBytes];
+            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
+            setUnsignedInt32LittleEndian(
+                    totalChunkCount,
+                    concatenationOfChunkCountAndChunkDigests,
+                    1);
+            digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
+        }
+
+        byte[] chunkContentPrefix = new byte[5];
+        chunkContentPrefix[0] = (byte) 0xa5;
+        int chunkIndex = 0;
+        MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            String jcaAlgorithmName =
+                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
+            try {
+                mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+            }
+        }
+        // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
+        // into how to parallelize (if at all) based on the capabilities of the hardware on which
+        // this code is running and based on the size of input.
+        DataDigester digester = new MultipleDigestDataDigester(mds);
+        int dataSourceIndex = 0;
+        for (DataSource input : contents) {
+            long inputOffset = 0;
+            long inputRemaining = input.size();
+            while (inputRemaining > 0) {
+                int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
+                setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
+                for (int i = 0; i < mds.length; i++) {
+                    mds[i].update(chunkContentPrefix);
+                }
+                try {
+                    input.feedIntoDataDigester(digester, inputOffset, chunkSize);
+                } catch (IOException e) {
+                    throw new DigestException(
+                            "Failed to digest chunk #" + chunkIndex + " of section #"
+                                    + dataSourceIndex,
+                            e);
+                }
+                for (int i = 0; i < digestAlgorithms.length; i++) {
+                    int digestAlgorithm = digestAlgorithms[i];
+                    byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
+                    int expectedDigestSizeBytes =
+                            getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+                    MessageDigest md = mds[i];
+                    int actualDigestSizeBytes =
+                            md.digest(
+                                    concatenationOfChunkCountAndChunkDigests,
+                                    5 + chunkIndex * expectedDigestSizeBytes,
+                                    expectedDigestSizeBytes);
+                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {
+                        throw new RuntimeException(
+                                "Unexpected output size of " + md.getAlgorithm() + " digest: "
+                                        + actualDigestSizeBytes);
+                    }
+                }
+                inputOffset += chunkSize;
+                inputRemaining -= chunkSize;
+                chunkIndex++;
+            }
+            dataSourceIndex++;
+        }
+
+        byte[][] result = new byte[digestAlgorithms.length][];
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            byte[] input = digestsOfChunks[i];
+            String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
+            MessageDigest md;
+            try {
+                md = MessageDigest.getInstance(jcaAlgorithmName);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+            }
+            byte[] output = md.digest(input);
+            result[i] = output;
+        }
+        return result;
+    }
+
+    /**
+     * Return the verity digest only if the length of digest content looks correct.
+     * When verity digest is generated, the last incomplete 4k chunk is padded with 0s before
+     * hashing. This means two almost identical APKs with different number of 0 at the end will have
+     * the same verity digest. To avoid this problem, the length of the source content (excluding
+     * Signing Block) is appended to the verity digest, and the digest is returned only if the
+     * length is consistent to the current APK.
+     */
+    static byte[] parseVerityDigestAndVerifySourceLength(
+            byte[] data, long fileSize, SignatureInfo signatureInfo) throws SecurityException {
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint8[32]  Merkle tree root hash of SHA-256
+        // * @+32 bytes int64      Length of source data
+        int kRootHashSize = 32;
+        int kSourceLengthSize = 8;
+
+        if (data.length != kRootHashSize + kSourceLengthSize) {
+            throw new SecurityException("Verity digest size is wrong: " + data.length);
+        }
+        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+        buffer.position(kRootHashSize);
+        long expectedSourceLength = buffer.getLong();
+
+        long signingBlockSize = signatureInfo.centralDirOffset
+                - signatureInfo.apkSigningBlockOffset;
+        if (expectedSourceLength != fileSize - signingBlockSize) {
+            throw new SecurityException("APK content size did not verify");
+        }
+
+        return Arrays.copyOfRange(data, 0, kRootHashSize);
+    }
+
+    private static void verifyIntegrityForVerityBasedAlgorithm(
+            byte[] expectedDigest,
+            RandomAccessFile apk,
+            SignatureInfo signatureInfo) throws SecurityException {
+        try {
+            byte[] expectedRootHash = parseVerityDigestAndVerifySourceLength(expectedDigest,
+                    apk.length(), signatureInfo);
+            VerityBuilder.VerityResult verity = VerityBuilder.generateApkVerityTree(apk,
+                    signatureInfo, new ByteBufferFactory() {
+                        @Override
+                        public ByteBuffer create(int capacity) {
+                            return ByteBuffer.allocate(capacity);
+                        }
+                    });
+            if (!Arrays.equals(expectedRootHash, verity.rootHash)) {
+                throw new SecurityException("APK verity digest of contents did not verify");
+            }
+        } catch (DigestException | IOException | NoSuchAlgorithmException e) {
+            throw new SecurityException("Error during verification", e);
+        }
+    }
+
+    /**
+     * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
+     *
+     * @throws IOException if an I/O error occurs while reading the file.
+     * @throws SignatureNotFoundException if the EoCD could not be found.
+     */
+    static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
+            throws IOException, SignatureNotFoundException {
+        Pair<ByteBuffer, Long> eocdAndOffsetInFile =
+                ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
+        if (eocdAndOffsetInFile == null) {
+            throw new SignatureNotFoundException(
+                    "Not an APK file: ZIP End of Central Directory record not found");
+        }
+        return eocdAndOffsetInFile;
+    }
+
+    static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
+            throws SignatureNotFoundException {
+        // Look up the offset of ZIP Central Directory.
+        long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
+        if (centralDirOffset > eocdOffset) {
+            throw new SignatureNotFoundException(
+                    "ZIP Central Directory offset out of range: " + centralDirOffset
+                    + ". ZIP End of Central Directory offset: " + eocdOffset);
+        }
+        long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
+        if (centralDirOffset + centralDirSize != eocdOffset) {
+            throw new SignatureNotFoundException(
+                    "ZIP Central Directory is not immediately followed by End of Central"
+                    + " Directory");
+        }
+        return centralDirOffset;
+    }
+
+    private static long getChunkCount(long inputSizeBytes) {
+        return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
+    }
+
+    private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
+
+    static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
+    static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
+    static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
+    static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
+    static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
+    static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
+    static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
+    static final int SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0421;
+    static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0423;
+    static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0425;
+
+    static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
+    static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
+    static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
+
+    private static final int[] V4_CONTENT_DIGEST_ALGORITHMS =
+            {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
+                    CONTENT_DIGEST_CHUNKED_SHA256};
+
+    static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
+        int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
+        int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
+        return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
+    }
+
+    private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
+        switch (digestAlgorithm1) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                        return 0;
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                    case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                        return -1;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                    case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                        return 1;
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                        return 0;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
+            case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                        return -1;
+                    case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                        return 0;
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                        return 1;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
+            default:
+                throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
+        }
+    }
+
+    static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_DSA_WITH_SHA256:
+                return CONTENT_DIGEST_CHUNKED_SHA256;
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return CONTENT_DIGEST_CHUNKED_SHA512;
+            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+            case SIGNATURE_VERITY_DSA_WITH_SHA256:
+                return CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+            case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                return "SHA-256";
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                return "SHA-512";
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown content digest algorthm: " + digestAlgorithm);
+        }
+    }
+
+    private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+            case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                return 256 / 8;
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                return 512 / 8;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown content digest algorthm: " + digestAlgorithm);
+        }
+    }
+
+    static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+                return "RSA";
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+                return "EC";
+            case SIGNATURE_DSA_WITH_SHA256:
+            case SIGNATURE_VERITY_DSA_WITH_SHA256:
+                return "DSA";
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    static Pair<String, ? extends AlgorithmParameterSpec>
+            getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+                return Pair.create(
+                        "SHA256withRSA/PSS",
+                        new PSSParameterSpec(
+                                "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+                return Pair.create(
+                        "SHA512withRSA/PSS",
+                        new PSSParameterSpec(
+                                "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
+                return Pair.create("SHA256withRSA", null);
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+                return Pair.create("SHA512withRSA", null);
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
+                return Pair.create("SHA256withECDSA", null);
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return Pair.create("SHA512withECDSA", null);
+            case SIGNATURE_DSA_WITH_SHA256:
+            case SIGNATURE_VERITY_DSA_WITH_SHA256:
+                return Pair.create("SHA256withDSA", null);
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    /**
+     * Returns the best digest from the map of available digests.
+     * similarly to compareContentDigestAlgorithm.
+     *
+     * Keep in sync with pickBestDigestForV4 in apksigner's ApkSigningBlockUtils.
+     */
+    static byte[] pickBestDigestForV4(Map<Integer, byte[]> contentDigests) {
+        for (int algo : V4_CONTENT_DIGEST_ALGORITHMS) {
+            if (contentDigests.containsKey(algo)) {
+                return contentDigests.get(algo);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns new byte buffer whose content is a shared subsequence of this buffer's content
+     * between the specified start (inclusive) and end (exclusive) positions. As opposed to
+     * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
+     * buffer's byte order.
+     */
+    static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
+        if (start < 0) {
+            throw new IllegalArgumentException("start: " + start);
+        }
+        if (end < start) {
+            throw new IllegalArgumentException("end < start: " + end + " < " + start);
+        }
+        int capacity = source.capacity();
+        if (end > source.capacity()) {
+            throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
+        }
+        int originalLimit = source.limit();
+        int originalPosition = source.position();
+        try {
+            source.position(0);
+            source.limit(end);
+            source.position(start);
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            return result;
+        } finally {
+            source.position(0);
+            source.limit(originalLimit);
+            source.position(originalPosition);
+        }
+    }
+
+    /**
+     * Relative <em>get</em> method for reading {@code size} number of bytes from the current
+     * position of this buffer.
+     *
+     * <p>This method reads the next {@code size} bytes at this buffer's current position,
+     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
+     * {@code size}, byte order set to this buffer's byte order; and then increments the position by
+     * {@code size}.
+     */
+    static ByteBuffer getByteBuffer(ByteBuffer source, int size)
+            throws BufferUnderflowException {
+        if (size < 0) {
+            throw new IllegalArgumentException("size: " + size);
+        }
+        int originalLimit = source.limit();
+        int position = source.position();
+        int limit = position + size;
+        if ((limit < position) || (limit > originalLimit)) {
+            throw new BufferUnderflowException();
+        }
+        source.limit(limit);
+        try {
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            source.position(limit);
+            return result;
+        } finally {
+            source.limit(originalLimit);
+        }
+    }
+
+    static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
+        if (source.remaining() < 4) {
+            throw new IOException(
+                    "Remaining buffer too short to contain length of length-prefixed field."
+                            + " Remaining: " + source.remaining());
+        }
+        int len = source.getInt();
+        if (len < 0) {
+            throw new IllegalArgumentException("Negative length");
+        } else if (len > source.remaining()) {
+            throw new IOException("Length-prefixed field longer than remaining buffer."
+                    + " Field length: " + len + ", remaining: " + source.remaining());
+        }
+        return getByteBuffer(source, len);
+    }
+
+    static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
+        int len = buf.getInt();
+        if (len < 0) {
+            throw new IOException("Negative length");
+        } else if (len > buf.remaining()) {
+            throw new IOException("Underflow while reading length-prefixed value. Length: " + len
+                    + ", available: " + buf.remaining());
+        }
+        byte[] result = new byte[len];
+        buf.get(result);
+        return result;
+    }
+
+    static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
+        result[offset] = (byte) (value & 0xff);
+        result[offset + 1] = (byte) ((value >>> 8) & 0xff);
+        result[offset + 2] = (byte) ((value >>> 16) & 0xff);
+        result[offset + 3] = (byte) ((value >>> 24) & 0xff);
+    }
+
+    private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
+    private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
+    private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
+
+    static Pair<ByteBuffer, Long> findApkSigningBlock(
+            RandomAccessFile apk, long centralDirOffset)
+                    throws IOException, SignatureNotFoundException {
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes payload
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+
+        if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
+            throw new SignatureNotFoundException(
+                    "APK too small for APK Signing Block. ZIP Central Directory offset: "
+                            + centralDirOffset);
+        }
+        // Read the magic and offset in file from the footer section of the block:
+        // * uint64:   size of block
+        // * 16 bytes: magic
+        ByteBuffer footer = ByteBuffer.allocate(24);
+        footer.order(ByteOrder.LITTLE_ENDIAN);
+        apk.seek(centralDirOffset - footer.capacity());
+        apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
+        if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
+                || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
+            throw new SignatureNotFoundException(
+                    "No APK Signing Block before ZIP Central Directory");
+        }
+        // Read and compare size fields
+        long apkSigBlockSizeInFooter = footer.getLong(0);
+        if ((apkSigBlockSizeInFooter < footer.capacity())
+                || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
+        }
+        int totalSize = (int) (apkSigBlockSizeInFooter + 8);
+        long apkSigBlockOffset = centralDirOffset - totalSize;
+        if (apkSigBlockOffset < 0) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block offset out of range: " + apkSigBlockOffset);
+        }
+        ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
+        apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
+        apk.seek(apkSigBlockOffset);
+        apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
+        long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
+        if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block sizes in header and footer do not match: "
+                            + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
+        }
+        return Pair.create(apkSigBlock, apkSigBlockOffset);
+    }
+
+    static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
+            throws SignatureNotFoundException {
+        checkByteOrderLittleEndian(apkSigningBlock);
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes pairs
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
+
+        int entryCount = 0;
+        while (pairs.hasRemaining()) {
+            entryCount++;
+            if (pairs.remaining() < 8) {
+                throw new SignatureNotFoundException(
+                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
+            }
+            long lenLong = pairs.getLong();
+            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount
+                                + " size out of range: " + lenLong);
+            }
+            int len = (int) lenLong;
+            int nextEntryPos = pairs.position() + len;
+            if (len > pairs.remaining()) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
+                                + ", available: " + pairs.remaining());
+            }
+            int id = pairs.getInt();
+            if (id == blockId) {
+                return getByteBuffer(pairs, len - 4);
+            }
+            pairs.position(nextEntryPos);
+        }
+
+        throw new SignatureNotFoundException(
+                "No block with ID " + blockId + " in APK Signing Block.");
+    }
+
+    private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
+        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+        }
+    }
+
+    /**
+     * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed.
+     */
+    private static class MultipleDigestDataDigester implements DataDigester {
+        private final MessageDigest[] mMds;
+
+        MultipleDigestDataDigester(MessageDigest[] mds) {
+            mMds = mds;
+        }
+
+        @Override
+        public void consume(ByteBuffer buffer) {
+            buffer = buffer.slice();
+            for (MessageDigest md : mMds) {
+                buffer.position(0);
+                md.update(buffer);
+            }
+        }
+    }
+
+}
diff --git a/android/util/apk/ByteBufferDataSource.java b/android/util/apk/ByteBufferDataSource.java
new file mode 100644
index 0000000..3976568
--- /dev/null
+++ b/android/util/apk/ByteBufferDataSource.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.apk;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+
+/**
+ * {@link DataSource} which provides data from a {@link ByteBuffer}.
+ */
+class ByteBufferDataSource implements DataSource {
+    /**
+     * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
+     * The buffer's position is 0 and limit is equal to capacity.
+     */
+    private final ByteBuffer mBuf;
+
+    ByteBufferDataSource(ByteBuffer buf) {
+        // Defensive copy, to avoid changes to mBuf being visible in buf, and to ensure position is
+        // 0 and limit == capacity.
+        mBuf = buf.slice();
+    }
+
+    @Override
+    public long size() {
+        return mBuf.capacity();
+    }
+
+    @Override
+    public void feedIntoDataDigester(DataDigester md, long offset, int size)
+            throws IOException, DigestException {
+        // There's no way to tell MessageDigest to read data from ByteBuffer from a position
+        // other than the buffer's current position. We thus need to change the buffer's
+        // position to match the requested offset.
+        //
+        // In the future, it may be necessary to compute digests of multiple regions in
+        // parallel. Given that digest computation is a slow operation, we enable multiple
+        // such requests to be fulfilled by this instance. This is achieved by serially
+        // creating a new ByteBuffer corresponding to the requested data range and then,
+        // potentially concurrently, feeding these buffers into MessageDigest instances.
+        ByteBuffer region;
+        synchronized (mBuf) {
+            mBuf.position(0);
+            mBuf.limit((int) offset + size);
+            mBuf.position((int) offset);
+            region = mBuf.slice();
+        }
+
+        md.consume(region);
+    }
+}
diff --git a/android/util/apk/ByteBufferFactory.java b/android/util/apk/ByteBufferFactory.java
new file mode 100644
index 0000000..7a99882
--- /dev/null
+++ b/android/util/apk/ByteBufferFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 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.apk;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Provider of {@link ByteBuffer} instances.
+ * @hide
+ */
+public interface ByteBufferFactory {
+    /** Initiates a {@link ByteBuffer} with the given size. */
+    ByteBuffer create(int capacity);
+}
diff --git a/android/util/apk/DataDigester.java b/android/util/apk/DataDigester.java
new file mode 100644
index 0000000..18d1dff
--- /dev/null
+++ b/android/util/apk/DataDigester.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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.apk;
+
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+
+interface DataDigester {
+    /** Consumes the {@link ByteBuffer}. */
+    void consume(ByteBuffer buffer) throws DigestException;
+}
diff --git a/android/util/apk/DataSource.java b/android/util/apk/DataSource.java
new file mode 100644
index 0000000..82f3800
--- /dev/null
+++ b/android/util/apk/DataSource.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.apk;
+
+import java.io.IOException;
+import java.security.DigestException;
+
+/** Source of data to be digested. */
+interface DataSource {
+
+    /**
+     * Returns the size (in bytes) of the data offered by this source.
+     */
+    long size();
+
+    /**
+     * Feeds the specified region of this source's data into the provided digester.
+     *
+     * @param offset offset of the region inside this data source.
+     * @param size size (in bytes) of the region.
+     */
+    void feedIntoDataDigester(DataDigester md, long offset, int size)
+            throws IOException, DigestException;
+}
diff --git a/android/util/apk/MemoryMappedFileDataSource.java b/android/util/apk/MemoryMappedFileDataSource.java
new file mode 100644
index 0000000..8d2b1e3
--- /dev/null
+++ b/android/util/apk/MemoryMappedFileDataSource.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.apk;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.DirectByteBuffer;
+import java.security.DigestException;
+
+/**
+ * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
+ * of the file.
+ */
+class MemoryMappedFileDataSource implements DataSource {
+    private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
+
+    private final FileDescriptor mFd;
+    private final long mFilePosition;
+    private final long mSize;
+
+    /**
+     * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
+     *
+     * @param position start position of the region in the file.
+     * @param size size (in bytes) of the region.
+     */
+    MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
+        mFd = fd;
+        mFilePosition = position;
+        mSize = size;
+    }
+
+    @Override
+    public long size() {
+        return mSize;
+    }
+
+    @Override
+    public void feedIntoDataDigester(DataDigester md, long offset, int size)
+            throws IOException, DigestException {
+        // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
+        // method was settled on a straightforward mmap with prefaulting.
+        //
+        // This method is not using FileChannel.map API because that API does not offset a way
+        // to "prefault" the resulting memory pages. Without prefaulting, performance is about
+        // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
+        // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
+        // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
+        // time, which is not compensated for by faster reads.
+
+        // We mmap the smallest region of the file containing the requested data. mmap requires
+        // that the start offset in the file must be a multiple of memory page size. We thus may
+        // need to mmap from an offset less than the requested offset.
+        long filePosition = mFilePosition + offset;
+        long mmapFilePosition =
+                (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
+        int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
+        long mmapRegionSize = size + dataStartOffsetInMmapRegion;
+        long mmapPtr = 0;
+        try {
+            mmapPtr = Os.mmap(
+                    0, // let the OS choose the start address of the region in memory
+                    mmapRegionSize,
+                    OsConstants.PROT_READ,
+                    OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
+                    mFd,
+                    mmapFilePosition);
+            ByteBuffer buf = new DirectByteBuffer(
+                    size,
+                    mmapPtr + dataStartOffsetInMmapRegion,
+                    mFd,  // not really needed, but just in case
+                    null, // no need to clean up -- it's taken care of by the finally block
+                    true  // read only buffer
+                    );
+            md.consume(buf);
+        } catch (ErrnoException e) {
+            throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
+        } finally {
+            if (mmapPtr != 0) {
+                try {
+                    Os.munmap(mmapPtr, mmapRegionSize);
+                } catch (ErrnoException ignored) { }
+            }
+        }
+    }
+}
diff --git a/android/util/apk/SignatureInfo.java b/android/util/apk/SignatureInfo.java
new file mode 100644
index 0000000..8e1233a
--- /dev/null
+++ b/android/util/apk/SignatureInfo.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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.apk;
+
+import java.nio.ByteBuffer;
+
+/**
+ * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
+ * contained in the block against the file.
+ */
+class SignatureInfo {
+    /** Contents of APK Signature Scheme v2 block. */
+    public final ByteBuffer signatureBlock;
+
+    /** Position of the APK Signing Block in the file. */
+    public final long apkSigningBlockOffset;
+
+    /** Position of the ZIP Central Directory in the file. */
+    public final long centralDirOffset;
+
+    /** Position of the ZIP End of Central Directory (EoCD) in the file. */
+    public final long eocdOffset;
+
+    /** Contents of ZIP End of Central Directory (EoCD) of the file. */
+    public final ByteBuffer eocd;
+
+    SignatureInfo(ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset,
+            long eocdOffset, ByteBuffer eocd) {
+        this.signatureBlock = signatureBlock;
+        this.apkSigningBlockOffset = apkSigningBlockOffset;
+        this.centralDirOffset = centralDirOffset;
+        this.eocdOffset = eocdOffset;
+        this.eocd = eocd;
+    }
+}
diff --git a/android/util/apk/SignatureNotFoundException.java b/android/util/apk/SignatureNotFoundException.java
new file mode 100644
index 0000000..9c7c760
--- /dev/null
+++ b/android/util/apk/SignatureNotFoundException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.apk;
+
+/**
+ * Indicates that the APK is missing a signature.
+ *
+ * @hide
+ */
+public class SignatureNotFoundException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    public SignatureNotFoundException(String message) {
+        super(message);
+    }
+
+    public SignatureNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/android/util/apk/SourceStampVerificationResult.java b/android/util/apk/SourceStampVerificationResult.java
new file mode 100644
index 0000000..2edaf62
--- /dev/null
+++ b/android/util/apk/SourceStampVerificationResult.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 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.apk;
+
+import android.annotation.Nullable;
+
+import java.security.cert.Certificate;
+
+/**
+ * A class encapsulating the result from the source stamp verifier
+ *
+ * <p>It indicates whether the source stamp is verified or not, and the source stamp certificate.
+ *
+ * @hide
+ */
+public final class SourceStampVerificationResult {
+
+    private final boolean mPresent;
+    private final boolean mVerified;
+    private final Certificate mCertificate;
+
+    private SourceStampVerificationResult(
+            boolean present, boolean verified, @Nullable Certificate certificate) {
+        this.mPresent = present;
+        this.mVerified = verified;
+        this.mCertificate = certificate;
+    }
+
+    public boolean isPresent() {
+        return mPresent;
+    }
+
+    public boolean isVerified() {
+        return mVerified;
+    }
+
+    public Certificate getCertificate() {
+        return mCertificate;
+    }
+
+    /**
+     * Create a non-present source stamp outcome.
+     *
+     * @return A non-present source stamp result.
+     */
+    public static SourceStampVerificationResult notPresent() {
+        return new SourceStampVerificationResult(
+                /* present= */ false, /* verified= */ false, /* certificate= */ null);
+    }
+
+    /**
+     * Create a verified source stamp outcome.
+     *
+     * @param certificate The source stamp certificate.
+     * @return A verified source stamp result, and the source stamp certificate.
+     */
+    public static SourceStampVerificationResult verified(Certificate certificate) {
+        return new SourceStampVerificationResult(
+                /* present= */ true, /* verified= */ true, certificate);
+    }
+
+    /**
+     * Create a non-verified source stamp outcome.
+     *
+     * @return A non-verified source stamp result.
+     */
+    public static SourceStampVerificationResult notVerified() {
+        return new SourceStampVerificationResult(
+                /* present= */ true, /* verified= */ false, /* certificate= */ null);
+    }
+}
diff --git a/android/util/apk/SourceStampVerifier.java b/android/util/apk/SourceStampVerifier.java
new file mode 100644
index 0000000..a7ae32d
--- /dev/null
+++ b/android/util/apk/SourceStampVerifier.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2020 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.apk;
+
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
+import android.util.Pair;
+import android.util.Slog;
+import android.util.jar.StrictJarFile;
+
+import libcore.io.IoUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+
+/**
+ * Source Stamp verifier.
+ *
+ * <p>SourceStamp improves traceability of apps with respect to unauthorized distribution.
+ *
+ * <p>The stamp is part of the APK that is protected by the signing block.
+ *
+ * <p>The APK contents hash is signed using the stamp key, and is saved as part of the signing
+ * block.
+ *
+ * @hide for internal use only.
+ */
+public abstract class SourceStampVerifier {
+
+    private static final String TAG = "SourceStampVerifier";
+
+    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+    private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+    private static final int SOURCE_STAMP_BLOCK_ID = 0x2b09189e;
+
+    /** Name of the SourceStamp certificate hash ZIP entry in APKs. */
+    private static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256";
+
+    /** Hidden constructor to prevent instantiation. */
+    private SourceStampVerifier() {}
+
+    /** Verifies SourceStamp present in a list of APKs. */
+    public static SourceStampVerificationResult verify(List<String> apkFiles) {
+        Certificate stampCertificate = null;
+        for (String apkFile : apkFiles) {
+            SourceStampVerificationResult sourceStampVerificationResult = verify(apkFile);
+            if (!sourceStampVerificationResult.isPresent()
+                    || !sourceStampVerificationResult.isVerified()) {
+                return sourceStampVerificationResult;
+            }
+            if (stampCertificate != null
+                    && !stampCertificate.equals(sourceStampVerificationResult.getCertificate())) {
+                return SourceStampVerificationResult.notVerified();
+            }
+            stampCertificate = sourceStampVerificationResult.getCertificate();
+        }
+        return SourceStampVerificationResult.verified(stampCertificate);
+    }
+
+    /** Verifies SourceStamp present in the provided APK. */
+    public static SourceStampVerificationResult verify(String apkFile) {
+        StrictJarFile apkJar = null;
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            apkJar =
+                    new StrictJarFile(
+                            apkFile,
+                            /* verify= */ false,
+                            /* signatureSchemeRollbackProtectionsEnforced= */ false);
+            byte[] sourceStampCertificateDigest = getSourceStampCertificateDigest(apkJar);
+            if (sourceStampCertificateDigest == null) {
+                // SourceStamp certificate hash file not found, which means that there is not
+                // SourceStamp present.
+                return SourceStampVerificationResult.notPresent();
+            }
+            return verify(apk, sourceStampCertificateDigest);
+        } catch (IOException e) {
+            // Any exception in reading the APK returns a non-present SourceStamp outcome
+            // without affecting the outcome of any of the other signature schemes.
+            return SourceStampVerificationResult.notPresent();
+        } finally {
+            closeApkJar(apkJar);
+        }
+    }
+
+    private static SourceStampVerificationResult verify(
+            RandomAccessFile apk, byte[] sourceStampCertificateDigest) {
+        try {
+            SignatureInfo signatureInfo =
+                    ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID);
+            Map<Integer, byte[]> apkContentDigests = getApkContentDigests(apk);
+            return verify(signatureInfo, apkContentDigests, sourceStampCertificateDigest);
+        } catch (IOException | SignatureNotFoundException e) {
+            return SourceStampVerificationResult.notVerified();
+        }
+    }
+
+    private static SourceStampVerificationResult verify(
+            SignatureInfo signatureInfo,
+            Map<Integer, byte[]> apkContentDigests,
+            byte[] sourceStampCertificateDigest)
+            throws SecurityException, IOException {
+        CertificateFactory certFactory;
+        try {
+            certFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+
+        List<Pair<Integer, byte[]>> digests =
+                apkContentDigests.entrySet().stream()
+                        .sorted(Map.Entry.comparingByKey())
+                        .map(e -> Pair.create(e.getKey(), e.getValue()))
+                        .collect(Collectors.toList());
+        byte[] digestBytes = encodeApkContentDigests(digests);
+
+        ByteBuffer sourceStampBlock = signatureInfo.signatureBlock;
+        ByteBuffer sourceStampBlockData =
+                ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock);
+
+        // Parse the SourceStamp certificate.
+        byte[] sourceStampEncodedCertificate =
+                ApkSigningBlockUtils.readLengthPrefixedByteArray(sourceStampBlockData);
+        X509Certificate sourceStampCertificate;
+        try {
+            sourceStampCertificate =
+                    (X509Certificate)
+                            certFactory.generateCertificate(
+                                    new ByteArrayInputStream(sourceStampEncodedCertificate));
+        } catch (CertificateException e) {
+            throw new SecurityException("Failed to decode certificate", e);
+        }
+        sourceStampCertificate =
+                new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate);
+
+        // Verify the SourceStamp certificate found in the signing block is the same as the
+        // SourceStamp certificate found in the APK.
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+            messageDigest.update(sourceStampEncodedCertificate);
+            byte[] sourceStampBlockCertificateDigest = messageDigest.digest();
+            if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) {
+                throw new SecurityException("Certificate mismatch between APK and signature block");
+            }
+        } catch (NoSuchAlgorithmException e) {
+            throw new SecurityException("Failed to find SHA-256", e);
+        }
+
+        // Parse the signatures block and identify supported signatures
+        ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData);
+        int signatureCount = 0;
+        int bestSigAlgorithm = -1;
+        byte[] bestSigAlgorithmSignatureBytes = null;
+        while (signatures.hasRemaining()) {
+            signatureCount++;
+            try {
+                ByteBuffer signature = getLengthPrefixedSlice(signatures);
+                if (signature.remaining() < 8) {
+                    throw new SecurityException("Signature record too short");
+                }
+                int sigAlgorithm = signature.getInt();
+                if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
+                    continue;
+                }
+                if ((bestSigAlgorithm == -1)
+                        || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
+                    bestSigAlgorithm = sigAlgorithm;
+                    bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new SecurityException(
+                        "Failed to parse signature record #" + signatureCount, e);
+            }
+        }
+        if (bestSigAlgorithm == -1) {
+            if (signatureCount == 0) {
+                throw new SecurityException("No signatures found");
+            } else {
+                throw new SecurityException("No supported signatures found");
+            }
+        }
+
+        // Verify signatures over digests using the SourceStamp's certificate.
+        Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+                getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
+        String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+        AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+        PublicKey publicKey = sourceStampCertificate.getPublicKey();
+        boolean sigVerified;
+        try {
+            Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+            sig.initVerify(publicKey);
+            if (jcaSignatureAlgorithmParams != null) {
+                sig.setParameter(jcaSignatureAlgorithmParams);
+            }
+            sig.update(digestBytes);
+            sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
+        } catch (InvalidKeyException
+                | InvalidAlgorithmParameterException
+                | SignatureException
+                | NoSuchAlgorithmException e) {
+            throw new SecurityException(
+                    "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+        }
+        if (!sigVerified) {
+            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+        }
+
+        return SourceStampVerificationResult.verified(sourceStampCertificate);
+    }
+
+    private static Map<Integer, byte[]> getApkContentDigests(RandomAccessFile apk)
+            throws IOException, SignatureNotFoundException {
+        // Retrieve APK content digests in V3 signing block. If a V3 signature is not found, the APK
+        // content digests would be re-tried from V2 signature.
+        try {
+            SignatureInfo v3SignatureInfo =
+                    ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
+            return getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock);
+        } catch (SignatureNotFoundException e) {
+            // It's fine not to find a V3 signature.
+        }
+
+        // Retrieve APK content digests in V2 signing block. If a V2 signature is not found, the
+        // process of retrieving APK content digests stops, and the stamp is considered un-verified.
+        SignatureInfo v2SignatureInfo =
+                ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
+        return getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock);
+    }
+
+    private static Map<Integer, byte[]> getApkContentDigestsFromSignatureBlock(
+            ByteBuffer signatureBlock) throws IOException {
+        Map<Integer, byte[]> apkContentDigests = new HashMap<>();
+        ByteBuffer signers = getLengthPrefixedSlice(signatureBlock);
+        while (signers.hasRemaining()) {
+            ByteBuffer signer = getLengthPrefixedSlice(signers);
+            ByteBuffer signedData = getLengthPrefixedSlice(signer);
+            ByteBuffer digests = getLengthPrefixedSlice(signedData);
+            while (digests.hasRemaining()) {
+                ByteBuffer digest = getLengthPrefixedSlice(digests);
+                int sigAlgorithm = digest.getInt();
+                byte[] contentDigest = readLengthPrefixedByteArray(digest);
+                int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm);
+                apkContentDigests.put(digestAlgorithm, contentDigest);
+            }
+        }
+        return apkContentDigests;
+    }
+
+    private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException {
+        InputStream inputStream = null;
+        try {
+            ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME);
+            if (zipEntry == null) {
+                // SourceStamp certificate hash file not found, which means that there is not
+                // SourceStamp present.
+                return null;
+            }
+            inputStream = apkJar.getInputStream(zipEntry);
+            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+            // Trying to read the certificate digest, which should be less than 1024 bytes.
+            byte[] buffer = new byte[1024];
+            int count = inputStream.read(buffer, 0, buffer.length);
+            byteArrayOutputStream.write(buffer, 0, count);
+
+            return byteArrayOutputStream.toByteArray();
+        } finally {
+            IoUtils.closeQuietly(inputStream);
+        }
+    }
+
+    private static byte[] encodeApkContentDigests(List<Pair<Integer, byte[]>> apkContentDigests) {
+        int resultSize = 0;
+        for (Pair<Integer, byte[]> element : apkContentDigests) {
+            resultSize += 12 + element.second.length;
+        }
+        ByteBuffer result = ByteBuffer.allocate(resultSize);
+        result.order(ByteOrder.LITTLE_ENDIAN);
+        for (Pair<Integer, byte[]> element : apkContentDigests) {
+            byte[] second = element.second;
+            result.putInt(8 + second.length);
+            result.putInt(element.first);
+            result.putInt(second.length);
+            result.put(second);
+        }
+        return result.array();
+    }
+
+    private static void closeApkJar(StrictJarFile apkJar) {
+        try {
+            if (apkJar == null) {
+                return;
+            }
+            apkJar.close();
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not close APK jar", e);
+        }
+    }
+}
diff --git a/android/util/apk/VerbatimX509Certificate.java b/android/util/apk/VerbatimX509Certificate.java
new file mode 100644
index 0000000..391c5fc
--- /dev/null
+++ b/android/util/apk/VerbatimX509Certificate.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 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.apk;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+/**
+ * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
+ * of letting the underlying implementation have a shot at re-encoding the data.
+ */
+class VerbatimX509Certificate extends WrappedX509Certificate {
+    private final byte[] mEncodedVerbatim;
+    private int mHash = -1;
+
+    VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
+        super(wrapped);
+        this.mEncodedVerbatim = encodedVerbatim;
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return mEncodedVerbatim;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof VerbatimX509Certificate)) return false;
+
+        try {
+            byte[] a = this.getEncoded();
+            byte[] b = ((VerbatimX509Certificate) o).getEncoded();
+            return Arrays.equals(a, b);
+        } catch (CertificateEncodingException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        if (mHash == -1) {
+            try {
+                mHash = Arrays.hashCode(this.getEncoded());
+            } catch (CertificateEncodingException e) {
+                mHash = 0;
+            }
+        }
+        return mHash;
+    }
+}
diff --git a/android/util/apk/VerityBuilder.java b/android/util/apk/VerityBuilder.java
new file mode 100644
index 0000000..e81e3f7
--- /dev/null
+++ b/android/util/apk/VerityBuilder.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2017 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.apk;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+
+/**
+ * VerityBuilder builds the verity Merkle tree and other metadata.  The generated tree format can
+ * be stored on disk for fs-verity setup and used by kernel.  The builder support standard
+ * fs-verity, and Android specific apk-verity that requires additional kernel patches.
+ *
+ * <p>Unlike a regular Merkle tree of fs-verity, the apk-verity tree does not cover the file content
+ * fully, and has to skip APK Signing Block with some special treatment for the "Central Directory
+ * offset" field of ZIP End of Central Directory.
+ *
+ * @hide
+ */
+public abstract class VerityBuilder {
+    private VerityBuilder() {}
+
+    private static final int CHUNK_SIZE_BYTES = 4096;  // Typical Linux block size
+    private static final int DIGEST_SIZE_BYTES = 32;  // SHA-256 size
+    private static final int FSVERITY_HEADER_SIZE_BYTES = 64;
+    private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE = 4;
+    private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
+    private static final String JCA_DIGEST_ALGORITHM = "SHA-256";
+    private static final byte[] DEFAULT_SALT = new byte[8];
+
+    /** Result generated by the builder. */
+    public static class VerityResult {
+        /** Raw fs-verity metadata and Merkle tree ready to be deployed on disk. */
+        public final ByteBuffer verityData;
+
+        /** Size of the Merkle tree in {@code verityData}. */
+        public final int merkleTreeSize;
+
+        /** Root hash of the Merkle tree. */
+        public final byte[] rootHash;
+
+        private VerityResult(ByteBuffer verityData, int merkleTreeSize, byte[] rootHash) {
+            this.verityData = verityData;
+            this.merkleTreeSize = merkleTreeSize;
+            this.rootHash = rootHash;
+        }
+    }
+
+    /**
+     * Generates the 4k, SHA-256 based Merkle tree for the given APK and stores in the {@link
+     * ByteBuffer} created by the {@link ByteBufferFactory}.  The Merkle tree does not cover Signing
+     * Block specificed in {@code signatureInfo}.  The output is suitable to be used as the on-disk
+     * format for fs-verity to use (with elide and patch extensions).
+     *
+     * @return VerityResult containing a buffer with the generated Merkle tree stored at the
+     *         front, the tree size, and the calculated root hash.
+     */
+    @NonNull
+    public static VerityResult generateApkVerityTree(@NonNull RandomAccessFile apk,
+            @Nullable SignatureInfo signatureInfo, @NonNull ByteBufferFactory bufferFactory)
+            throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
+        return generateVerityTreeInternal(apk, bufferFactory, signatureInfo);
+    }
+
+    @NonNull
+    private static VerityResult generateVerityTreeInternal(@NonNull RandomAccessFile apk,
+            @NonNull ByteBufferFactory bufferFactory, @Nullable SignatureInfo signatureInfo)
+            throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
+        long signingBlockSize =
+                signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
+        long dataSize = apk.length() - signingBlockSize;
+        int[] levelOffset = calculateVerityLevelOffset(dataSize);
+        int merkleTreeSize = levelOffset[levelOffset.length - 1];
+
+        ByteBuffer output = bufferFactory.create(
+                merkleTreeSize
+                + CHUNK_SIZE_BYTES);  // maximum size of apk-verity metadata
+        output.order(ByteOrder.LITTLE_ENDIAN);
+        ByteBuffer tree = slice(output, 0, merkleTreeSize);
+        byte[] apkRootHash = generateVerityTreeInternal(apk, signatureInfo, DEFAULT_SALT,
+                levelOffset, tree);
+        return new VerityResult(output, merkleTreeSize, apkRootHash);
+    }
+
+    static void generateApkVerityFooter(@NonNull RandomAccessFile apk,
+            @NonNull SignatureInfo signatureInfo, @NonNull ByteBuffer footerOutput)
+            throws IOException {
+        footerOutput.order(ByteOrder.LITTLE_ENDIAN);
+        generateApkVerityHeader(footerOutput, apk.length(), DEFAULT_SALT);
+        long signingBlockSize =
+                signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
+        generateApkVerityExtensions(footerOutput, signatureInfo.apkSigningBlockOffset,
+                signingBlockSize, signatureInfo.eocdOffset);
+    }
+
+    /**
+     * Calculates the apk-verity root hash for integrity measurement.  This needs to be consistent
+     * to what kernel returns.
+     */
+    @NonNull
+    static byte[] generateApkVerityRootHash(@NonNull RandomAccessFile apk,
+            @NonNull ByteBuffer apkDigest, @NonNull SignatureInfo signatureInfo)
+            throws NoSuchAlgorithmException, DigestException, IOException {
+        assertSigningBlockAlignedAndHasFullPages(signatureInfo);
+
+        ByteBuffer footer = ByteBuffer.allocate(CHUNK_SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN);
+        generateApkVerityFooter(apk, signatureInfo, footer);
+        footer.flip();
+
+        MessageDigest md = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
+        md.update(footer);
+        md.update(apkDigest);
+        return md.digest();
+    }
+
+    /**
+     * Generates the apk-verity header and hash tree to be used by kernel for the given apk. This
+     * method does not check whether the root hash exists in the Signing Block or not.
+     *
+     * <p>The output is stored in the {@link ByteBuffer} created by the given {@link
+     * ByteBufferFactory}.
+     *
+     * @return the root hash of the generated hash tree.
+     */
+    @NonNull
+    static byte[] generateApkVerity(@NonNull String apkPath,
+            @NonNull ByteBufferFactory bufferFactory, @NonNull SignatureInfo signatureInfo)
+            throws IOException, SignatureNotFoundException, SecurityException, DigestException,
+                   NoSuchAlgorithmException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+            VerityResult result = generateVerityTreeInternal(apk, bufferFactory, signatureInfo);
+            ByteBuffer footer = slice(result.verityData, result.merkleTreeSize,
+                    result.verityData.limit());
+            generateApkVerityFooter(apk, signatureInfo, footer);
+            // Put the reverse offset to apk-verity header at the end.
+            footer.putInt(footer.position() + 4);
+            result.verityData.limit(result.merkleTreeSize + footer.position());
+            return result.rootHash;
+        }
+    }
+
+    /**
+     * A helper class to consume and digest data by block continuously, and write into a buffer.
+     */
+    private static class BufferedDigester implements DataDigester {
+        /** Amount of the data to digest in each cycle before writting out the digest. */
+        private static final int BUFFER_SIZE = CHUNK_SIZE_BYTES;
+
+        /**
+         * Amount of data the {@link MessageDigest} has consumed since the last reset. This must be
+         * always less than BUFFER_SIZE since {@link MessageDigest} is reset whenever it has
+         * consumed BUFFER_SIZE of data.
+         */
+        private int mBytesDigestedSinceReset;
+
+        /** The final output {@link ByteBuffer} to write the digest to sequentially. */
+        private final ByteBuffer mOutput;
+
+        private final MessageDigest mMd;
+        private final byte[] mDigestBuffer = new byte[DIGEST_SIZE_BYTES];
+        private final byte[] mSalt;
+
+        private BufferedDigester(@Nullable byte[] salt, @NonNull ByteBuffer output)
+                throws NoSuchAlgorithmException {
+            mSalt = salt;
+            mOutput = output.slice();
+            mMd = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
+            if (mSalt != null) {
+                mMd.update(mSalt);
+            }
+            mBytesDigestedSinceReset = 0;
+        }
+
+        /**
+         * Consumes and digests data up to BUFFER_SIZE (may continue from the previous remaining),
+         * then writes the final digest to the output buffer.  Repeat until all data are consumed.
+         * If the last consumption is not enough for BUFFER_SIZE, the state will stay and future
+         * consumption will continuous from there.
+         */
+        @Override
+        public void consume(ByteBuffer buffer) throws DigestException {
+            int offset = buffer.position();
+            int remaining = buffer.remaining();
+            while (remaining > 0) {
+                int allowance = (int) Math.min(remaining, BUFFER_SIZE - mBytesDigestedSinceReset);
+                // Optimization: set the buffer limit to avoid allocating a new ByteBuffer object.
+                buffer.limit(buffer.position() + allowance);
+                mMd.update(buffer);
+                offset += allowance;
+                remaining -= allowance;
+                mBytesDigestedSinceReset += allowance;
+
+                if (mBytesDigestedSinceReset == BUFFER_SIZE) {
+                    mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
+                    mOutput.put(mDigestBuffer);
+                    // After digest, MessageDigest resets automatically, so no need to reset again.
+                    if (mSalt != null) {
+                        mMd.update(mSalt);
+                    }
+                    mBytesDigestedSinceReset = 0;
+                }
+            }
+        }
+
+        public void assertEmptyBuffer() throws DigestException {
+            if (mBytesDigestedSinceReset != 0) {
+                throw new IllegalStateException("Buffer is not empty: " + mBytesDigestedSinceReset);
+            }
+        }
+
+        private void fillUpLastOutputChunk() {
+            int lastBlockSize = (int) (mOutput.position() % BUFFER_SIZE);
+            if (lastBlockSize == 0) {
+                return;
+            }
+            mOutput.put(ByteBuffer.allocate(BUFFER_SIZE - lastBlockSize));
+        }
+    }
+
+    /**
+     * Digest the source by chunk in the given range.  If the last chunk is not a full chunk,
+     * digest the remaining.
+     */
+    private static void consumeByChunk(DataDigester digester, DataSource source, int chunkSize)
+            throws IOException, DigestException {
+        long inputRemaining = source.size();
+        long inputOffset = 0;
+        while (inputRemaining > 0) {
+            int size = (int) Math.min(inputRemaining, chunkSize);
+            source.feedIntoDataDigester(digester, inputOffset, size);
+            inputOffset += size;
+            inputRemaining -= size;
+        }
+    }
+
+    // Rationale: 1) 1 MB should fit in memory space on all devices. 2) It is not too granular
+    // thus the syscall overhead is not too big.
+    private static final int MMAP_REGION_SIZE_BYTES = 1024 * 1024;
+
+    private static void generateFsVerityDigestAtLeafLevel(RandomAccessFile file, ByteBuffer output)
+            throws IOException, NoSuchAlgorithmException, DigestException {
+        BufferedDigester digester = new BufferedDigester(null /* salt */, output);
+
+        // 1. Digest the whole file by chunks.
+        consumeByChunk(digester,
+                new MemoryMappedFileDataSource(file.getFD(), 0, file.length()),
+                MMAP_REGION_SIZE_BYTES);
+
+        // 2. Pad 0s up to the nearest 4096-byte block before hashing.
+        int lastIncompleteChunkSize = (int) (file.length() % CHUNK_SIZE_BYTES);
+        if (lastIncompleteChunkSize != 0) {
+            digester.consume(ByteBuffer.allocate(CHUNK_SIZE_BYTES - lastIncompleteChunkSize));
+        }
+        digester.assertEmptyBuffer();
+
+        // 3. Fill up the rest of buffer with 0s.
+        digester.fillUpLastOutputChunk();
+    }
+
+    private static void generateApkVerityDigestAtLeafLevel(RandomAccessFile apk,
+            SignatureInfo signatureInfo, byte[] salt, ByteBuffer output)
+            throws IOException, NoSuchAlgorithmException, DigestException {
+        BufferedDigester digester = new BufferedDigester(salt, output);
+
+        // 1. Digest from the beginning of the file, until APK Signing Block is reached.
+        consumeByChunk(digester,
+                new MemoryMappedFileDataSource(apk.getFD(), 0, signatureInfo.apkSigningBlockOffset),
+                MMAP_REGION_SIZE_BYTES);
+
+        // 2. Skip APK Signing Block and continue digesting, until the Central Directory offset
+        // field in EoCD is reached.
+        long eocdCdOffsetFieldPosition =
+                signatureInfo.eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET;
+        consumeByChunk(digester,
+                new MemoryMappedFileDataSource(apk.getFD(), signatureInfo.centralDirOffset,
+                    eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset),
+                MMAP_REGION_SIZE_BYTES);
+
+        // 3. Consume offset of Signing Block as an alternative EoCD.
+        ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate(
+                ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+        alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset));
+        alternativeCentralDirOffset.flip();
+        digester.consume(alternativeCentralDirOffset);
+
+        // 4. Read from end of the Central Directory offset field in EoCD to the end of the file.
+        long offsetAfterEocdCdOffsetField =
+                eocdCdOffsetFieldPosition + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+        consumeByChunk(digester,
+                new MemoryMappedFileDataSource(apk.getFD(), offsetAfterEocdCdOffsetField,
+                    apk.length() - offsetAfterEocdCdOffsetField),
+                MMAP_REGION_SIZE_BYTES);
+
+        // 5. Pad 0s up to the nearest 4096-byte block before hashing.
+        int lastIncompleteChunkSize = (int) (apk.length() % CHUNK_SIZE_BYTES);
+        if (lastIncompleteChunkSize != 0) {
+            digester.consume(ByteBuffer.allocate(CHUNK_SIZE_BYTES - lastIncompleteChunkSize));
+        }
+        digester.assertEmptyBuffer();
+
+        // 6. Fill up the rest of buffer with 0s.
+        digester.fillUpLastOutputChunk();
+    }
+
+    @NonNull
+    private static byte[] generateVerityTreeInternal(@NonNull RandomAccessFile apk,
+            @Nullable SignatureInfo signatureInfo, @Nullable byte[] salt,
+            @NonNull int[] levelOffset, @NonNull ByteBuffer output)
+            throws IOException, NoSuchAlgorithmException, DigestException {
+        // 1. Digest the apk to generate the leaf level hashes.
+        assertSigningBlockAlignedAndHasFullPages(signatureInfo);
+        generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output,
+                    levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1]));
+
+        // 2. Digest the lower level hashes bottom up.
+        for (int level = levelOffset.length - 3; level >= 0; level--) {
+            ByteBuffer inputBuffer = slice(output, levelOffset[level + 1], levelOffset[level + 2]);
+            ByteBuffer outputBuffer = slice(output, levelOffset[level], levelOffset[level + 1]);
+
+            DataSource source = new ByteBufferDataSource(inputBuffer);
+            BufferedDigester digester = new BufferedDigester(salt, outputBuffer);
+            consumeByChunk(digester, source, CHUNK_SIZE_BYTES);
+            digester.assertEmptyBuffer();
+            digester.fillUpLastOutputChunk();
+        }
+
+        // 3. Digest the first block (i.e. first level) to generate the root hash.
+        byte[] rootHash = new byte[DIGEST_SIZE_BYTES];
+        BufferedDigester digester = new BufferedDigester(salt, ByteBuffer.wrap(rootHash));
+        digester.consume(slice(output, 0, CHUNK_SIZE_BYTES));
+        digester.assertEmptyBuffer();
+        return rootHash;
+    }
+
+    private static ByteBuffer generateApkVerityHeader(ByteBuffer buffer, long fileSize,
+            byte[] salt) {
+        if (salt.length != 8) {
+            throw new IllegalArgumentException("salt is not 8 bytes long");
+        }
+
+        // TODO(b/30972906): update the reference when there is a better one in public.
+        buffer.put("TrueBrew".getBytes());  // magic
+
+        buffer.put((byte) 1);               // major version
+        buffer.put((byte) 0);               // minor version
+        buffer.put((byte) 12);              // log2(block-size): log2(4096)
+        buffer.put((byte) 7);               // log2(leaves-per-node): log2(4096 / 32)
+
+        buffer.putShort((short) 1);         // meta algorithm, SHA256 == 1
+        buffer.putShort((short) 1);         // data algorithm, SHA256 == 1
+
+        buffer.putInt(0);                   // flags
+        buffer.putInt(0);                   // reserved
+
+        buffer.putLong(fileSize);           // original file size
+
+        buffer.put((byte) 2);               // authenticated extension count
+        buffer.put((byte) 0);               // unauthenticated extension count
+        buffer.put(salt);                   // salt (8 bytes)
+        skip(buffer, 22);                   // reserved
+
+        return buffer;
+    }
+
+    private static ByteBuffer generateApkVerityExtensions(ByteBuffer buffer,
+            long signingBlockOffset, long signingBlockSize, long eocdOffset) {
+        // Snapshot of the experimental fs-verity structs (different from upstream).
+        //
+        // struct fsverity_extension_elide {
+        //   __le64 offset;
+        //   __le64 length;
+        // }
+        //
+        // struct fsverity_extension_patch {
+        //   __le64 offset;
+        //   u8 databytes[];
+        // };
+
+        final int kSizeOfFsverityExtensionHeader = 8;
+        final int kExtensionSizeAlignment = 8;
+
+        {
+            // struct fsverity_extension #1
+            final int kSizeOfFsverityElidedExtension = 16;
+
+            // First field is total size of extension, padded to 64-bit alignment
+            buffer.putInt(kSizeOfFsverityExtensionHeader + kSizeOfFsverityElidedExtension);
+            buffer.putShort((short) 1);  // ID of elide extension
+            skip(buffer, 2);             // reserved
+
+            // struct fsverity_extension_elide
+            buffer.putLong(signingBlockOffset);
+            buffer.putLong(signingBlockSize);
+        }
+
+        {
+            // struct fsverity_extension #2
+            final int kTotalSize = kSizeOfFsverityExtensionHeader
+                    + 8 // offset size
+                    + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+
+            buffer.putInt(kTotalSize);   // Total size of extension, padded to 64-bit alignment
+            buffer.putShort((short) 2);  // ID of patch extension
+            skip(buffer, 2);             // reserved
+
+            // struct fsverity_extension_patch
+            buffer.putLong(eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);  // offset
+            buffer.putInt(Math.toIntExact(signingBlockOffset));  // databytes
+
+            // The extension needs to be 0-padded at the end, since the length may not be multiple
+            // of 8.
+            int kPadding = kExtensionSizeAlignment - kTotalSize % kExtensionSizeAlignment;
+            if (kPadding == kExtensionSizeAlignment) {
+                kPadding = 0;
+            }
+            skip(buffer, kPadding);      // padding
+        }
+
+        return buffer;
+    }
+
+    /**
+     * Returns an array of summed area table of level size in the verity tree.  In other words, the
+     * returned array is offset of each level in the verity tree file format, plus an additional
+     * offset of the next non-existing level (i.e. end of the last level + 1).  Thus the array size
+     * is level + 1.  Thus, the returned array is guarantee to have at least 2 elements.
+     */
+    private static int[] calculateVerityLevelOffset(long fileSize) {
+        ArrayList<Long> levelSize = new ArrayList<>();
+        while (true) {
+            long levelDigestSize = divideRoundup(fileSize, CHUNK_SIZE_BYTES) * DIGEST_SIZE_BYTES;
+            long chunksSize = CHUNK_SIZE_BYTES * divideRoundup(levelDigestSize, CHUNK_SIZE_BYTES);
+            levelSize.add(chunksSize);
+            if (levelDigestSize <= CHUNK_SIZE_BYTES) {
+                break;
+            }
+            fileSize = levelDigestSize;
+        }
+
+        // Reverse and convert to summed area table.
+        int[] levelOffset = new int[levelSize.size() + 1];
+        levelOffset[0] = 0;
+        for (int i = 0; i < levelSize.size(); i++) {
+            // We don't support verity tree if it is larger then Integer.MAX_VALUE.
+            levelOffset[i + 1] = levelOffset[i]
+                    + Math.toIntExact(levelSize.get(levelSize.size() - i - 1));
+        }
+        return levelOffset;
+    }
+
+    private static void assertSigningBlockAlignedAndHasFullPages(
+            @NonNull SignatureInfo signatureInfo) {
+        if (signatureInfo.apkSigningBlockOffset % CHUNK_SIZE_BYTES != 0) {
+            throw new IllegalArgumentException(
+                    "APK Signing Block does not start at the page boundary: "
+                    + signatureInfo.apkSigningBlockOffset);
+        }
+
+        if ((signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset)
+                % CHUNK_SIZE_BYTES != 0) {
+            throw new IllegalArgumentException(
+                    "Size of APK Signing Block is not a multiple of 4096: "
+                    + (signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset));
+        }
+    }
+
+    /** Returns a slice of the buffer which shares content with the provided buffer. */
+    private static ByteBuffer slice(ByteBuffer buffer, int begin, int end) {
+        ByteBuffer b = buffer.duplicate();
+        b.position(0);  // to ensure position <= limit invariant.
+        b.limit(end);
+        b.position(begin);
+        return b.slice();
+    }
+
+    /** Skip the {@code ByteBuffer} position by {@code bytes}. */
+    private static void skip(ByteBuffer buffer, int bytes) {
+        buffer.position(buffer.position() + bytes);
+    }
+
+    /** Divides a number and round up to the closest integer. */
+    private static long divideRoundup(long dividend, long divisor) {
+        return (dividend + divisor - 1) / divisor;
+    }
+}
diff --git a/android/util/apk/WrappedX509Certificate.java b/android/util/apk/WrappedX509Certificate.java
new file mode 100644
index 0000000..fdaa420
--- /dev/null
+++ b/android/util/apk/WrappedX509Certificate.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 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.apk;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+class WrappedX509Certificate extends X509Certificate {
+    private final X509Certificate mWrapped;
+
+    WrappedX509Certificate(X509Certificate wrapped) {
+        this.mWrapped = wrapped;
+    }
+
+    @Override
+    public Set<String> getCriticalExtensionOIDs() {
+        return mWrapped.getCriticalExtensionOIDs();
+    }
+
+    @Override
+    public byte[] getExtensionValue(String oid) {
+        return mWrapped.getExtensionValue(oid);
+    }
+
+    @Override
+    public Set<String> getNonCriticalExtensionOIDs() {
+        return mWrapped.getNonCriticalExtensionOIDs();
+    }
+
+    @Override
+    public boolean hasUnsupportedCriticalExtension() {
+        return mWrapped.hasUnsupportedCriticalExtension();
+    }
+
+    @Override
+    public void checkValidity()
+            throws CertificateExpiredException, CertificateNotYetValidException {
+        mWrapped.checkValidity();
+    }
+
+    @Override
+    public void checkValidity(Date date)
+            throws CertificateExpiredException, CertificateNotYetValidException {
+        mWrapped.checkValidity(date);
+    }
+
+    @Override
+    public int getVersion() {
+        return mWrapped.getVersion();
+    }
+
+    @Override
+    public BigInteger getSerialNumber() {
+        return mWrapped.getSerialNumber();
+    }
+
+    @Override
+    public Principal getIssuerDN() {
+        return mWrapped.getIssuerDN();
+    }
+
+    @Override
+    public Principal getSubjectDN() {
+        return mWrapped.getSubjectDN();
+    }
+
+    @Override
+    public Date getNotBefore() {
+        return mWrapped.getNotBefore();
+    }
+
+    @Override
+    public Date getNotAfter() {
+        return mWrapped.getNotAfter();
+    }
+
+    @Override
+    public byte[] getTBSCertificate() throws CertificateEncodingException {
+        return mWrapped.getTBSCertificate();
+    }
+
+    @Override
+    public byte[] getSignature() {
+        return mWrapped.getSignature();
+    }
+
+    @Override
+    public String getSigAlgName() {
+        return mWrapped.getSigAlgName();
+    }
+
+    @Override
+    public String getSigAlgOID() {
+        return mWrapped.getSigAlgOID();
+    }
+
+    @Override
+    public byte[] getSigAlgParams() {
+        return mWrapped.getSigAlgParams();
+    }
+
+    @Override
+    public boolean[] getIssuerUniqueID() {
+        return mWrapped.getIssuerUniqueID();
+    }
+
+    @Override
+    public boolean[] getSubjectUniqueID() {
+        return mWrapped.getSubjectUniqueID();
+    }
+
+    @Override
+    public boolean[] getKeyUsage() {
+        return mWrapped.getKeyUsage();
+    }
+
+    @Override
+    public int getBasicConstraints() {
+        return mWrapped.getBasicConstraints();
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return mWrapped.getEncoded();
+    }
+
+    @Override
+    public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
+        mWrapped.verify(key);
+    }
+
+    @Override
+    public void verify(PublicKey key, String sigProvider)
+            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+            NoSuchProviderException, SignatureException {
+        mWrapped.verify(key, sigProvider);
+    }
+
+    @Override
+    public String toString() {
+        return mWrapped.toString();
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        return mWrapped.getPublicKey();
+    }
+}
diff --git a/android/util/apk/ZipUtils.java b/android/util/apk/ZipUtils.java
new file mode 100644
index 0000000..fa5477e
--- /dev/null
+++ b/android/util/apk/ZipUtils.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2016 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.apk;
+
+import android.util.Pair;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Assorted ZIP format helpers.
+ *
+ * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte
+ * order of these buffers is little-endian.
+ */
+abstract class ZipUtils {
+    private ZipUtils() {}
+
+    private static final int ZIP_EOCD_REC_MIN_SIZE = 22;
+    private static final int ZIP_EOCD_REC_SIG = 0x06054b50;
+    private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12;
+    private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
+    private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
+
+    private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
+    private static final int ZIP64_EOCD_LOCATOR_SIG_REVERSE_BYTE_ORDER = 0x504b0607;
+
+    private static final int UINT16_MAX_VALUE = 0xffff;
+
+    /**
+     * Returns the ZIP End of Central Directory record of the provided ZIP file.
+     *
+     * @return contents of the ZIP End of Central Directory record and the record's offset in the
+     *         file or {@code null} if the file does not contain the record.
+     *
+     * @throws IOException if an I/O error occurs while reading the file.
+     */
+    static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord(RandomAccessFile zip)
+            throws IOException {
+        // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
+        // The record can be identified by its 4-byte signature/magic which is located at the very
+        // beginning of the record. A complication is that the record is variable-length because of
+        // the comment field.
+        // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
+        // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
+        // the candidate record's comment length is such that the remainder of the record takes up
+        // exactly the remaining bytes in the buffer. The search is bounded because the maximum
+        // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
+
+        long fileSize = zip.length();
+        if (fileSize < ZIP_EOCD_REC_MIN_SIZE) {
+            return null;
+        }
+
+        // Optimization: 99.99% of APKs have a zero-length comment field in the EoCD record and thus
+        // the EoCD record offset is known in advance. Try that offset first to avoid unnecessarily
+        // reading more data.
+        Pair<ByteBuffer, Long> result = findZipEndOfCentralDirectoryRecord(zip, 0);
+        if (result != null) {
+            return result;
+        }
+
+        // EoCD does not start where we expected it to. Perhaps it contains a non-empty comment
+        // field. Expand the search. The maximum size of the comment field in EoCD is 65535 because
+        // the comment length field is an unsigned 16-bit number.
+        return findZipEndOfCentralDirectoryRecord(zip, UINT16_MAX_VALUE);
+    }
+
+    /**
+     * Returns the ZIP End of Central Directory record of the provided ZIP file.
+     *
+     * @param maxCommentSize maximum accepted size (in bytes) of EoCD comment field. The permitted
+     *        value is from 0 to 65535 inclusive. The smaller the value, the faster this method
+     *        locates the record, provided its comment field is no longer than this value.
+     *
+     * @return contents of the ZIP End of Central Directory record and the record's offset in the
+     *         file or {@code null} if the file does not contain the record.
+     *
+     * @throws IOException if an I/O error occurs while reading the file.
+     */
+    private static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord(
+            RandomAccessFile zip, int maxCommentSize) throws IOException {
+        // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
+        // The record can be identified by its 4-byte signature/magic which is located at the very
+        // beginning of the record. A complication is that the record is variable-length because of
+        // the comment field.
+        // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
+        // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
+        // the candidate record's comment length is such that the remainder of the record takes up
+        // exactly the remaining bytes in the buffer. The search is bounded because the maximum
+        // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
+
+        if ((maxCommentSize < 0) || (maxCommentSize > UINT16_MAX_VALUE)) {
+            throw new IllegalArgumentException("maxCommentSize: " + maxCommentSize);
+        }
+
+        long fileSize = zip.length();
+        if (fileSize < ZIP_EOCD_REC_MIN_SIZE) {
+            // No space for EoCD record in the file.
+            return null;
+        }
+        // Lower maxCommentSize if the file is too small.
+        maxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_REC_MIN_SIZE);
+
+        ByteBuffer buf = ByteBuffer.allocate(ZIP_EOCD_REC_MIN_SIZE + maxCommentSize);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        long bufOffsetInFile = fileSize - buf.capacity();
+        zip.seek(bufOffsetInFile);
+        zip.readFully(buf.array(), buf.arrayOffset(), buf.capacity());
+        int eocdOffsetInBuf = findZipEndOfCentralDirectoryRecord(buf);
+        if (eocdOffsetInBuf == -1) {
+            // No EoCD record found in the buffer
+            return null;
+        }
+        // EoCD found
+        buf.position(eocdOffsetInBuf);
+        ByteBuffer eocd = buf.slice();
+        eocd.order(ByteOrder.LITTLE_ENDIAN);
+        return Pair.create(eocd, bufOffsetInFile + eocdOffsetInBuf);
+    }
+
+    /**
+     * Returns the position at which ZIP End of Central Directory record starts in the provided
+     * buffer or {@code -1} if the record is not present.
+     *
+     * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
+     */
+    private static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {
+        assertByteOrderLittleEndian(zipContents);
+
+        // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
+        // The record can be identified by its 4-byte signature/magic which is located at the very
+        // beginning of the record. A complication is that the record is variable-length because of
+        // the comment field.
+        // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
+        // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
+        // the candidate record's comment length is such that the remainder of the record takes up
+        // exactly the remaining bytes in the buffer. The search is bounded because the maximum
+        // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
+
+        int archiveSize = zipContents.capacity();
+        if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
+            return -1;
+        }
+        int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);
+        int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
+        for (int expectedCommentLength = 0; expectedCommentLength <= maxCommentLength;
+                expectedCommentLength++) {
+            int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
+            if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
+                int actualCommentLength =
+                        getUnsignedInt16(
+                                zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);
+                if (actualCommentLength == expectedCommentLength) {
+                    return eocdStartPos;
+                }
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Returns {@code true} if the provided file contains a ZIP64 End of Central Directory
+     * Locator.
+     *
+     * @param zipEndOfCentralDirectoryPosition offset of the ZIP End of Central Directory record
+     *        in the file.
+     *
+     * @throws IOException if an I/O error occurs while reading the file.
+     */
+    public static final boolean isZip64EndOfCentralDirectoryLocatorPresent(
+            RandomAccessFile zip, long zipEndOfCentralDirectoryPosition) throws IOException {
+
+        // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central
+        // Directory Record.
+        long locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
+        if (locatorPosition < 0) {
+            return false;
+        }
+
+        zip.seek(locatorPosition);
+        // RandomAccessFile.readInt assumes big-endian byte order, but ZIP format uses
+        // little-endian.
+        return zip.readInt() == ZIP64_EOCD_LOCATOR_SIG_REVERSE_BYTE_ORDER;
+    }
+
+    /**
+     * Returns the offset of the start of the ZIP Central Directory in the archive.
+     *
+     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+     */
+    public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) {
+        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+        return getUnsignedInt32(
+                zipEndOfCentralDirectory,
+                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);
+    }
+
+    /**
+     * Sets the offset of the start of the ZIP Central Directory in the archive.
+     *
+     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+     */
+    public static void setZipEocdCentralDirectoryOffset(
+            ByteBuffer zipEndOfCentralDirectory, long offset) {
+        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+        setUnsignedInt32(
+                zipEndOfCentralDirectory,
+                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET,
+                offset);
+    }
+
+    /**
+     * Returns the size (in bytes) of the ZIP Central Directory.
+     *
+     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+     */
+    public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
+        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+        return getUnsignedInt32(
+                zipEndOfCentralDirectory,
+                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
+    }
+
+    private static void assertByteOrderLittleEndian(ByteBuffer buffer) {
+        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+        }
+    }
+
+    private static int getUnsignedInt16(ByteBuffer buffer, int offset) {
+        return buffer.getShort(offset) & 0xffff;
+    }
+
+    private static long getUnsignedInt32(ByteBuffer buffer, int offset) {
+        return buffer.getInt(offset) & 0xffffffffL;
+    }
+
+    private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {
+        if ((value < 0) || (value > 0xffffffffL)) {
+            throw new IllegalArgumentException("uint32 value of out range: " + value);
+        }
+        buffer.putInt(buffer.position() + offset, (int) value);
+    }
+}
diff --git a/android/util/imagepool/Bucket.java b/android/util/imagepool/Bucket.java
new file mode 100644
index 0000000..c562243
--- /dev/null
+++ b/android/util/imagepool/Bucket.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 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.imagepool;
+
+import com.android.tools.layoutlib.annotations.Nullable;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+
+import android.util.imagepool.ImagePool.Image.Orientation;
+
+import java.awt.image.BufferedImage;
+import java.lang.ref.SoftReference;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Data model for image pool. Bucket contains the list of same sized buffered image in soft ref.
+ */
+/* private package */ class Bucket {
+
+    @VisibleForTesting final Queue<SoftReference<BufferedImage>> mBufferedImageRef = new LinkedList<>();
+
+    public boolean isEmpty() {
+        return mBufferedImageRef.isEmpty();
+    }
+
+    @Nullable
+    public BufferedImage remove() {
+        if (mBufferedImageRef.isEmpty()) {
+            return null;
+        }
+
+        SoftReference<BufferedImage> reference = mBufferedImageRef.remove();
+        return reference == null ? null : reference.get();
+    }
+
+    public void offer(BufferedImage img) {
+        mBufferedImageRef.offer(new SoftReference<>(img));
+    }
+
+    public void clear() {
+        mBufferedImageRef.clear();
+    }
+
+    static class BucketCreationMetaData {
+        public final int mWidth;
+        public final int mHeight;
+        public final int mType;
+        public final int mNumberOfCopies;
+        public final Orientation mOrientation;
+        public final long mMaxCacheSize;
+
+        BucketCreationMetaData(int width, int height, int type, int numberOfCopies,
+                Orientation orientation, long maxCacheSize) {
+            mWidth = width;
+            mHeight = height;
+            mType = type;
+            mNumberOfCopies = numberOfCopies;
+            mOrientation = orientation;
+            mMaxCacheSize = maxCacheSize;
+        }
+    }
+}
\ No newline at end of file
diff --git a/android/util/imagepool/ImageImpl.java b/android/util/imagepool/ImageImpl.java
new file mode 100644
index 0000000..42a6e73
--- /dev/null
+++ b/android/util/imagepool/ImageImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 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.imagepool;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Representation of buffered image. When used with ImagePool, it provides 2 features
+ * (last one not yet impl'd):
+ *
+ * <ul>
+ *   <li>Automatic recycle of BufferedImage through use of reference counter</li>
+ *   <li>Able to re-use the image for similar size of buffered image</li>
+ *   <li>TODO: Able to re-use the iamge for different orientation (not yet impl'd)</li>
+ * </ul>
+ */
+/* private package */ class ImageImpl implements ImagePool.Image {
+
+    private final ReadWriteLock mLock = new ReentrantReadWriteLock();
+
+    private final int mWidth;
+    private final int mHeight;
+    private final Orientation mOrientation;
+
+    @VisibleForTesting final BufferedImage mImg;
+
+    ImageImpl(
+            int width,
+            int height,
+            @NotNull BufferedImage img,
+            Orientation orientation) {
+        mImg = img;
+        mWidth = width;
+        mHeight = height;
+        mOrientation = orientation;
+    }
+
+    @Override
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public void setRGB(int x, int y, int width, int height, int[] colors, int offset, int stride) {
+        mLock.readLock().lock();
+        try {
+            // TODO: Apply orientation.
+            mImg.setRGB(x, y, width, height, colors, offset, stride);
+        } finally {
+            mLock.readLock().unlock();
+        }
+    }
+
+    @Override
+    public void drawImage(Graphics2D graphics, int x, int y, @Nullable ImageObserver o) {
+        mLock.readLock().lock();
+        try {
+            // TODO: Apply orientation.
+            graphics.drawImage(mImg, x, y, mWidth, mHeight, o);
+        } finally {
+            mLock.readLock().unlock();
+        }
+    }
+}
\ No newline at end of file
diff --git a/android/util/imagepool/ImagePool.java b/android/util/imagepool/ImagePool.java
new file mode 100644
index 0000000..baec65d
--- /dev/null
+++ b/android/util/imagepool/ImagePool.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 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.imagepool;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
+import java.util.function.Consumer;
+
+/**
+ * Simplified version of image pool that exists in Studio.
+ *
+ * Lacks:
+ * - PhantomReference and FinalizableReference to recognize the death of references automatically.
+ *   (Meaning devs need to be more deligent in dispose.)
+ * Has:
+ * + Debugger that allows us to better trace where image is being leaked in stack.
+ */
+public interface ImagePool {
+
+    /**
+     * Returns a new image of width w and height h.
+     */
+    @NotNull
+    Image acquire(final int w, final int h, final int type);
+
+    /**
+     * Disposes the image pool, releasing all the references to the buffered images.
+     */
+    void dispose();
+
+    /**
+     * Interface that represents a buffered image. Using this wrapper allows us ot better track
+     * memory usages around BufferedImage. When all of it's references are removed, it will
+     * automatically be pooled back into the image pool for re-use.
+     */
+    interface Image {
+
+        /**
+         * Same as {@link BufferedImage#setRGB(int, int, int, int, int[], int, int)}
+         */
+        void setRGB(int x, int y, int width, int height, int[] colors, int offset, int stride);
+
+        /**
+         * Same as {@link Graphics2D#drawImage(java.awt.Image, int, int, ImageObserver)}
+         */
+        void drawImage(Graphics2D graphics, int x, int y, ImageObserver o);
+
+        /**
+         * Image orientation. It's not used at the moment. To be used later.
+         */
+        enum Orientation {
+            NONE,
+            CW_90
+        }
+
+        int getWidth();
+        int getHeight();
+    }
+
+    /**
+     * Policy for how to set up the memory pool.
+     */
+    class ImagePoolPolicy {
+
+        public final int[] mBucketSizes;
+        public final int[] mNumberOfCopies;
+        public final long mBucketMaxCacheSize;
+
+        /**
+         * @param bucketPixelSizes - list of pixel sizes to bucket (categorize) images. The list
+         * must be sorted (low to high).
+         * @param numberOfCopies - Allows users to create multiple copies of the bucket. It is
+         * recommended to create more copies for smaller images to avoid fragmentation in memory.
+         * It must match the size of bucketPixelSizes.
+         * @param bucketMaxCacheByteSize - Maximum cache byte sizes image pool is allowed to hold onto
+         * in memory.
+         */
+        public ImagePoolPolicy(
+                int[] bucketPixelSizes, int[] numberOfCopies, long bucketMaxCacheByteSize) {
+            assert bucketPixelSizes.length == numberOfCopies.length;
+            mBucketSizes = bucketPixelSizes;
+            mNumberOfCopies = numberOfCopies;
+            mBucketMaxCacheSize = bucketMaxCacheByteSize;
+        }
+    }
+}
diff --git a/android/util/imagepool/ImagePoolHelper.java b/android/util/imagepool/ImagePoolHelper.java
new file mode 100644
index 0000000..292fc59
--- /dev/null
+++ b/android/util/imagepool/ImagePoolHelper.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2018 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.imagepool;
+
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import android.util.imagepool.Bucket.BucketCreationMetaData;
+import android.util.imagepool.ImagePool.Image.Orientation;
+import android.util.imagepool.ImagePool.ImagePoolPolicy;
+
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+/* private package */ class ImagePoolHelper {
+
+    @Nullable
+    public static BucketCreationMetaData getBucketCreationMetaData(int w, int h, int type, ImagePoolPolicy poolPolicy, ImagePoolStats stats) {
+        // Find the bucket sizes for both dimensions
+        int widthBucket = -1;
+        int heightBucket = -1;
+        int index = 0;
+
+        for (int bucketMinSize : poolPolicy.mBucketSizes) {
+            if (widthBucket == -1 && w <= bucketMinSize) {
+                widthBucket = bucketMinSize;
+
+                if (heightBucket != -1) {
+                    break;
+                }
+            }
+            if (heightBucket == -1 && h <= bucketMinSize) {
+                heightBucket = bucketMinSize;
+
+                if (widthBucket != -1) {
+                    break;
+                }
+            }
+            ++index;
+        }
+
+        stats.recordBucketRequest(w, h);
+
+        if (index >= poolPolicy.mNumberOfCopies.length) {
+            return null;
+        }
+
+        // TODO: Apply orientation
+//        if (widthBucket < heightBucket) {
+//            return new BucketCreationMetaData(heightBucket, widthBucket, type, poolPolicy.mNumberOfCopies[index],
+//                    Orientation.CW_90, poolPolicy.mBucketMaxCacheSize);
+//        }
+        return new BucketCreationMetaData(widthBucket, heightBucket, type, poolPolicy.mNumberOfCopies[index],
+                Orientation.NONE, poolPolicy.mBucketMaxCacheSize);
+    }
+
+    @Nullable
+    public static BufferedImage getBufferedImage(
+            Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats) {
+
+        // strongref is just for gc.
+        BufferedImage strongRef = populateBucket(bucket, metaData, stats);
+
+        // pool is too small to create the requested buffer.
+        if (bucket.isEmpty()) {
+            assert strongRef == null;
+            return null;
+        }
+
+        // Go through the bucket of soft references to find the first buffer that's not null.
+        // Even if gc is triggered here, strongref should survive.
+        BufferedImage img = bucket.remove();
+        while (img == null && !bucket.isEmpty()) {
+            img = bucket.remove();
+        }
+
+        if (img == null && bucket.isEmpty()) {
+            // Whole buffer was null. Recurse.
+            return getBufferedImage(bucket, metaData, stats);
+        }
+        return img;
+    }
+
+    /**
+     * Populate the bucket in greedy manner to avoid fragmentation.
+     * Behaviour is controlled by {@link ImagePoolPolicy}.
+     * Returns one strong referenced buffer to avoid getting results gc'd. Null if pool is not large
+     * enough.
+     */
+    @Nullable
+    private static BufferedImage populateBucket(
+            Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats) {
+        if (!bucket.isEmpty()) {
+            // If not empty no need to populate.
+            return null;
+        }
+
+        BufferedImage strongRef = null;
+        for (int i = 0; i < metaData.mNumberOfCopies; i++) {
+            if (!stats.fitsMaxCacheSize(
+                    metaData.mWidth, metaData.mHeight, metaData.mMaxCacheSize)) {
+                break;
+            }
+            strongRef =new BufferedImage(metaData.mWidth, metaData.mHeight,
+                    metaData.mType);
+            bucket.offer(strongRef);
+            stats.recordBucketCreation(metaData.mWidth, metaData.mHeight);
+        }
+        return strongRef;
+    }
+
+    private static String toKey(int w, int h, int type) {
+        return new StringBuilder()
+                .append(w)
+                .append('x')
+                .append(h)
+                .append(':')
+                .append(type)
+                .toString();
+    }
+
+    public static Bucket getBucket(Map<String, Bucket> map, BucketCreationMetaData metaData, ImagePoolPolicy mPolicy) {
+        String key = toKey(metaData.mWidth, metaData.mHeight, metaData.mType);
+        Bucket bucket = map.get(key);
+        if (bucket == null) {
+            bucket = new Bucket();
+            map.put(key, bucket);
+        }
+        return bucket;
+    }
+}
\ No newline at end of file
diff --git a/android/util/imagepool/ImagePoolImpl.java b/android/util/imagepool/ImagePoolImpl.java
new file mode 100644
index 0000000..3da706e
--- /dev/null
+++ b/android/util/imagepool/ImagePoolImpl.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018 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.imagepool;
+
+import com.android.tools.layoutlib.annotations.Nullable;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+
+import android.util.imagepool.Bucket.BucketCreationMetaData;
+import android.util.imagepool.ImagePool.Image.Orientation;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.lang.ref.Reference;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Consumer;
+
+import com.google.common.base.FinalizablePhantomReference;
+import com.google.common.base.FinalizableReferenceQueue;
+
+class ImagePoolImpl implements ImagePool {
+
+    private final ReentrantReadWriteLock mReentrantLock = new ReentrantReadWriteLock();
+    private final ImagePoolPolicy mPolicy;
+    @VisibleForTesting final Map<String, Bucket> mPool = new HashMap<>();
+    @VisibleForTesting final ImagePoolStats mImagePoolStats = new ImagePoolStatsProdImpl();
+    private final FinalizableReferenceQueue mFinalizableReferenceQueue = new FinalizableReferenceQueue();
+    private final Set<Reference<?>> mReferences = new HashSet<>();
+
+    public ImagePoolImpl(ImagePoolPolicy policy) {
+        mPolicy = policy;
+        mImagePoolStats.start();
+    }
+
+    @Override
+    public Image acquire(int w, int h, int type) {
+        return acquire(w, h, type, null);
+    }
+
+    /* package private */ Image acquire(int w, int h, int type,
+            @Nullable Consumer<BufferedImage> freedCallback) {
+        mReentrantLock.writeLock().lock();
+        try {
+            BucketCreationMetaData metaData =
+                    ImagePoolHelper.getBucketCreationMetaData(w, h, type, mPolicy, mImagePoolStats);
+            if (metaData == null) {
+                return defaultImageImpl(w, h, type, freedCallback);
+            }
+
+            final Bucket existingBucket = ImagePoolHelper.getBucket(mPool, metaData, mPolicy);
+            final BufferedImage img =
+                    ImagePoolHelper.getBufferedImage(existingBucket, metaData, mImagePoolStats);
+            if (img == null) {
+                return defaultImageImpl(w, h, type, freedCallback);
+            }
+
+            // Clear the image. - is this necessary?
+            if (img.getRaster().getDataBuffer().getDataType() == java.awt.image.DataBuffer.TYPE_INT) {
+                Arrays.fill(((DataBufferInt)img.getRaster().getDataBuffer()).getData(), 0);
+            }
+
+            return prepareImage(
+                    new ImageImpl(w, h, img, metaData.mOrientation),
+                    true,
+                    img,
+                    existingBucket,
+                    freedCallback);
+        } finally {
+            mReentrantLock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Add statistics as well as dispose behaviour before returning image.
+     */
+    private Image prepareImage(
+            Image image,
+            boolean offerBackToBucket,
+            @Nullable BufferedImage img,
+            @Nullable Bucket existingBucket,
+            @Nullable Consumer<BufferedImage> freedCallback) {
+        final Integer imageHash = image.hashCode();
+        mImagePoolStats.acquiredImage(imageHash);
+        FinalizablePhantomReference<Image> reference =
+                new FinalizablePhantomReference<ImagePool.Image>(image, mFinalizableReferenceQueue) {
+                    @Override
+                    public void finalizeReferent() {
+                        // This method might be called twice if the user has manually called the free() method. The second call will have no effect.
+                        if (mReferences.remove(this)) {
+                            mImagePoolStats.disposeImage(imageHash);
+                            if (offerBackToBucket) {
+                                if (!mImagePoolStats.fitsMaxCacheSize(img.getWidth(), img.getHeight(),
+                                        mPolicy.mBucketMaxCacheSize)) {
+                                    mImagePoolStats.tooBigForCache();
+                                    // Adding this back would go over the max cache size we set for ourselves. Release it.
+                                    return;
+                                }
+
+                                // else stat does not change.
+                                existingBucket.offer(img);
+                            }
+                            if (freedCallback != null) {
+                                freedCallback.accept(img);
+                            }
+                        }
+                    }
+                };
+        mReferences.add(reference);
+        return image;
+    }
+
+    /**
+     * Default Image Impl to be used when the pool is not big enough.
+     */
+    private Image defaultImageImpl(int w, int h, int type,
+            @Nullable Consumer<BufferedImage> freedCallback) {
+        BufferedImage bufferedImage = new BufferedImage(w, h, type);
+        mImagePoolStats.tooBigForCache();
+        mImagePoolStats.recordAllocOutsidePool(w, h);
+        return prepareImage(new ImageImpl(w, h, bufferedImage, Orientation.NONE),
+                false,  null, null, freedCallback);
+    }
+
+    @Override
+    public void dispose() {
+        mReentrantLock.writeLock().lock();
+        try {
+            for (Bucket bucket : mPool.values()) {
+                bucket.clear();
+            }
+            mImagePoolStats.clear();
+        } finally {
+            mReentrantLock.writeLock().unlock();
+        }
+    }
+
+    /* package private */ void printStat() {
+        System.out.println(mImagePoolStats.getStatistic());
+    }
+}
\ No newline at end of file
diff --git a/android/util/imagepool/ImagePoolProvider.java b/android/util/imagepool/ImagePoolProvider.java
new file mode 100644
index 0000000..dbdd849
--- /dev/null
+++ b/android/util/imagepool/ImagePoolProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.imagepool;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import android.util.imagepool.ImagePool.ImagePoolPolicy;
+
+public class ImagePoolProvider {
+
+    private static ImagePool sInstance;
+
+    @NotNull
+    public static synchronized ImagePool get() {
+        if (sInstance == null) {
+            // Idea is to create more
+            ImagePoolPolicy policy = new ImagePoolPolicy(
+                    new int[]{100, 200, 400, 600, 800, 1000, 1600, 3200},
+                    new int[]{  3,   3,   2,   2,   2,    1,    1,    1},
+                    10_000_000L); // 10 MB
+
+            sInstance = new ImagePoolImpl(policy);
+        }
+        return sInstance;
+    }
+}
\ No newline at end of file
diff --git a/android/util/imagepool/ImagePoolStats.java b/android/util/imagepool/ImagePoolStats.java
new file mode 100644
index 0000000..17dab14
--- /dev/null
+++ b/android/util/imagepool/ImagePoolStats.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 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.imagepool;
+
+/**
+ * Keeps track of statistics. Some are purely for debugging purposes in which case it's wrapped
+ * around DEBUG check.
+ */
+interface ImagePoolStats {
+
+    void recordBucketCreation(int widthBucket, int heightBucket);
+
+    boolean fitsMaxCacheSize(int width, int height, long maxCacheSize);
+
+    void clear();
+
+    void recordBucketRequest(int w, int h);
+
+    void recordAllocOutsidePool(int width, int height);
+
+    void tooBigForCache();
+
+    void acquiredImage(Integer imageHash);
+
+    void disposeImage(Integer imageHash);
+
+    void start();
+
+    String getStatistic();
+}
diff --git a/android/util/imagepool/ImagePoolStatsDebugImpl.java b/android/util/imagepool/ImagePoolStatsDebugImpl.java
new file mode 100644
index 0000000..de0f757
--- /dev/null
+++ b/android/util/imagepool/ImagePoolStatsDebugImpl.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2018 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.imagepool;
+
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+
+/**
+ * Useful impl for debugging reproable error.
+ */
+public class ImagePoolStatsDebugImpl extends ImagePoolStatsProdImpl {
+
+    private static String PACKAGE_NAME = ImagePoolStats.class.getPackage().getName();
+
+    // Used for deugging purposes only.
+    private final Map<Integer, String> mCallStack = new HashMap<>();
+    private long mRequestedTotalBytes = 0;
+    private long mAllocatedOutsidePoolBytes = 0;
+
+    // Used for gc-related stats.
+    private long mPreviousGcCollection = 0;
+    private long mPreviousGcTime = 0;
+
+    /** Used for policy */
+    @Override
+    public void recordBucketCreation(int widthBucket, int heightBucket) {
+        super.recordBucketCreation(widthBucket, heightBucket);
+    }
+
+    @Override
+    public boolean fitsMaxCacheSize(int width, int height, long maxCacheSize) {
+        return super.fitsMaxCacheSize(width, height, maxCacheSize);
+    }
+
+    @Override
+    public void clear() {
+        super.clear();
+
+        mRequestedTotalBytes = 0;
+        mAllocatedOutsidePoolBytes = 0;
+        mTooBigForPoolCount = 0;
+        mCallStack.clear();
+    }
+
+    @Override
+    public void tooBigForCache() {
+        super.tooBigForCache();
+    }
+
+    /** Used for Debugging only */
+    @Override
+    public void recordBucketRequest(int w, int h) {
+        mRequestedTotalBytes += (w * h * ESTIMATED_PIXEL_BYTES);
+    }
+
+    @Override
+    public void recordAllocOutsidePool(int width, int height) {
+        mAllocatedOutsidePoolBytes += (width * height * ESTIMATED_PIXEL_BYTES);
+    }
+
+    @Override
+    public void acquiredImage(Integer imageHash) {
+        for (int i = 1; i < Thread.currentThread().getStackTrace().length; i++) {
+            StackTraceElement element = Thread.currentThread().getStackTrace()[i];
+            String str = element.toString();
+
+            if (!str.contains(PACKAGE_NAME)) {
+                mCallStack.put(imageHash, str);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void disposeImage(Integer imageHash) {
+        mCallStack.remove(imageHash);
+    }
+
+    @Override
+    public void start() {
+        long totalGarbageCollections = 0;
+        long garbageCollectionTime = 0;
+        for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
+            long count = gc.getCollectionCount();
+            if (count >= 0) {
+                totalGarbageCollections += count;
+            }
+            long time = gc.getCollectionTime();
+            if (time >= 0) {
+                garbageCollectionTime += time;
+            }
+        }
+        mPreviousGcCollection = totalGarbageCollections;
+        mPreviousGcTime = garbageCollectionTime;
+    }
+
+    private String calculateGcStatAndReturn() {
+        long totalGarbageCollections = 0;
+        long garbageCollectionTime = 0;
+        for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
+            long count = gc.getCollectionCount();
+            if (count > 0) {
+                totalGarbageCollections += count;
+            }
+            long time = gc.getCollectionTime();
+            if(time > 0) {
+                garbageCollectionTime += time;
+            }
+        }
+        totalGarbageCollections -= mPreviousGcCollection;
+        garbageCollectionTime -= mPreviousGcTime;
+
+        StringBuilder builder = new StringBuilder();
+        builder.append("Total Garbage Collections: ");
+        builder.append(totalGarbageCollections);
+        builder.append("\n");
+
+        builder.append("Total Garbage Collection Time (ms): ");
+        builder.append(garbageCollectionTime);
+        builder.append("\n");
+
+        return builder.toString();
+    }
+
+    @Override
+    public String getStatistic() {
+        StringBuilder builder = new StringBuilder();
+
+        builder.append(calculateGcStatAndReturn());
+        builder.append("Memory\n");
+        builder.append(" requested total         : ");
+        builder.append(mRequestedTotalBytes / 1_000_000);
+        builder.append(" MB\n");
+        builder.append(" allocated (in pool)     : ");
+        builder.append(mAllocateTotalBytes / 1_000_000);
+        builder.append(" MB\n");
+        builder.append(" allocated (out of pool) : ");
+        builder.append(mAllocatedOutsidePoolBytes / 1_000_000);
+        builder.append(" MB\n");
+
+        double percent = (1.0 - (double) mRequestedTotalBytes / (mAllocateTotalBytes +
+                mAllocatedOutsidePoolBytes));
+        if (percent < 0.0) {
+            builder.append(" saved : ");
+            builder.append(-1.0 * percent);
+            builder.append("%\n");
+        } else {
+            builder.append(" wasting : ");
+            builder.append(percent);
+            builder.append("%\n");
+        }
+
+        builder.append("Undispose images\n");
+        Multiset<String> countSet = HashMultiset.create();
+        for (String callsite : mCallStack.values()) {
+            countSet.add(callsite);
+        }
+
+        for (Multiset.Entry<String> entry : countSet.entrySet()) {
+            builder.append(" - ");
+            builder.append(entry.getElement());
+            builder.append(" - missed dispose : ");
+            builder.append(entry.getCount());
+            builder.append(" times\n");
+        }
+
+        builder.append("Number of times requested image didn't fit the pool : ");
+        builder.append(mTooBigForPoolCount);
+        builder.append("\n");
+
+        return builder.toString();
+    }
+}
diff --git a/android/util/imagepool/ImagePoolStatsProdImpl.java b/android/util/imagepool/ImagePoolStatsProdImpl.java
new file mode 100644
index 0000000..e6c6abe
--- /dev/null
+++ b/android/util/imagepool/ImagePoolStatsProdImpl.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 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.imagepool;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+
+class ImagePoolStatsProdImpl implements ImagePoolStats {
+
+    static int ESTIMATED_PIXEL_BYTES = 4;
+
+    // Used for determining how many buckets can be created.
+    @VisibleForTesting long mAllocateTotalBytes = 0;
+    @VisibleForTesting int mTooBigForPoolCount = 0;
+
+    /** Used for policy */
+    @Override
+    public void recordBucketCreation(int widthBucket, int heightBucket) {
+        mAllocateTotalBytes += (widthBucket * heightBucket * ESTIMATED_PIXEL_BYTES);
+    }
+
+    @Override
+    public boolean fitsMaxCacheSize(int width, int height, long maxCacheSize) {
+        long newTotal = mAllocateTotalBytes + (width * height * ESTIMATED_PIXEL_BYTES);
+        return newTotal <= maxCacheSize;
+    }
+
+    @Override
+    public void tooBigForCache() {
+        mTooBigForPoolCount++;
+    }
+
+    @Override
+    public void clear() {
+        mAllocateTotalBytes = 0;
+    }
+
+    @Override
+    public void recordBucketRequest(int w, int h) { }
+
+    @Override
+    public void recordAllocOutsidePool(int width, int height) { }
+
+    @Override
+    public void acquiredImage(@NotNull Integer imageHash) { }
+
+    @Override
+    public void disposeImage(@NotNull Integer imageHash) { }
+
+    @Override
+    public void start() { }
+
+    @Override
+    public String getStatistic() { return ""; }
+}
diff --git a/android/util/jar/StrictJarFile.java b/android/util/jar/StrictJarFile.java
new file mode 100644
index 0000000..11aee2f
--- /dev/null
+++ b/android/util/jar/StrictJarFile.java
@@ -0,0 +1,501 @@
+/*
+ * 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.jar;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import dalvik.system.CloseGuard;
+import java.io.FileDescriptor;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.jar.JarFile;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipEntry;
+import libcore.io.IoBridge;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * A subset of the JarFile API implemented as a thin wrapper over
+ * system/core/libziparchive.
+ *
+ * @hide for internal use only. Not API compatible (or as forgiving) as
+ *        {@link java.util.jar.JarFile}
+ */
+public final class StrictJarFile {
+
+    private final long nativeHandle;
+
+    // NOTE: It's possible to share a file descriptor with the native
+    // code, at the cost of some additional complexity.
+    private final FileDescriptor fd;
+
+    private final StrictJarManifest manifest;
+    private final StrictJarVerifier verifier;
+
+    private final boolean isSigned;
+
+    private final CloseGuard guard = CloseGuard.get();
+    private boolean closed;
+
+    public StrictJarFile(String fileName)
+            throws IOException, SecurityException {
+        this(fileName, true, true);
+    }
+
+    public StrictJarFile(FileDescriptor fd)
+            throws IOException, SecurityException {
+        this(fd, true, true);
+    }
+
+    public StrictJarFile(FileDescriptor fd,
+            boolean verify,
+            boolean signatureSchemeRollbackProtectionsEnforced)
+                    throws IOException, SecurityException {
+        this("[fd:" + fd.getInt$() + "]", fd, verify,
+                signatureSchemeRollbackProtectionsEnforced);
+    }
+
+    public StrictJarFile(String fileName,
+            boolean verify,
+            boolean signatureSchemeRollbackProtectionsEnforced)
+                    throws IOException, SecurityException {
+        this(fileName, IoBridge.open(fileName, OsConstants.O_RDONLY),
+                verify, signatureSchemeRollbackProtectionsEnforced);
+    }
+
+    /**
+     * @param name of the archive (not necessarily a path).
+     * @param fd seekable file descriptor for the JAR file.
+     * @param verify whether to verify the file's JAR signatures and collect the corresponding
+     *        signer certificates.
+     * @param signatureSchemeRollbackProtectionsEnforced {@code true} to enforce protections against
+     *        stripping newer signature schemes (e.g., APK Signature Scheme v2) from the file, or
+     *        {@code false} to ignore any such protections. This parameter is ignored when
+     *        {@code verify} is {@code false}.
+     */
+    private StrictJarFile(String name,
+            FileDescriptor fd,
+            boolean verify,
+            boolean signatureSchemeRollbackProtectionsEnforced)
+                    throws IOException, SecurityException {
+        this.nativeHandle = nativeOpenJarFile(name, fd.getInt$());
+        this.fd = fd;
+
+        try {
+            // Read the MANIFEST and signature files up front and try to
+            // parse them. We never want to accept a JAR File with broken signatures
+            // or manifests, so it's best to throw as early as possible.
+            if (verify) {
+                HashMap<String, byte[]> metaEntries = getMetaEntries();
+                this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
+                this.verifier =
+                        new StrictJarVerifier(
+                                name,
+                                manifest,
+                                metaEntries,
+                                signatureSchemeRollbackProtectionsEnforced);
+                Set<String> files = manifest.getEntries().keySet();
+                for (String file : files) {
+                    if (findEntry(file) == null) {
+                        throw new SecurityException("File " + file + " in manifest does not exist");
+                    }
+                }
+
+                isSigned = verifier.readCertificates() && verifier.isSignedJar();
+            } else {
+                isSigned = false;
+                this.manifest = null;
+                this.verifier = null;
+            }
+        } catch (IOException | SecurityException e) {
+            nativeClose(this.nativeHandle);
+            IoUtils.closeQuietly(fd);
+            closed = true;
+            throw e;
+        }
+
+        guard.open("close");
+    }
+
+    public StrictJarManifest getManifest() {
+        return manifest;
+    }
+
+    public Iterator<ZipEntry> iterator() throws IOException {
+        return new EntryIterator(nativeHandle, "");
+    }
+
+    public ZipEntry findEntry(String name) {
+        return nativeFindEntry(nativeHandle, name);
+    }
+
+    /**
+     * Return all certificate chains for a given {@link ZipEntry} belonging to this jar.
+     * This method MUST be called only after fully exhausting the InputStream belonging
+     * to this entry.
+     *
+     * Returns {@code null} if this jar file isn't signed or if this method is
+     * called before the stream is processed.
+     */
+    public Certificate[][] getCertificateChains(ZipEntry ze) {
+        if (isSigned) {
+            return verifier.getCertificateChains(ze.getName());
+        }
+
+        return null;
+    }
+
+    /**
+     * Return all certificates for a given {@link ZipEntry} belonging to this jar.
+     * This method MUST be called only after fully exhausting the InputStream belonging
+     * to this entry.
+     *
+     * Returns {@code null} if this jar file isn't signed or if this method is
+     * called before the stream is processed.
+     *
+     * @deprecated Switch callers to use getCertificateChains instead
+     */
+    @Deprecated
+    public Certificate[] getCertificates(ZipEntry ze) {
+        if (isSigned) {
+            Certificate[][] certChains = verifier.getCertificateChains(ze.getName());
+
+            // Measure number of certs.
+            int count = 0;
+            for (Certificate[] chain : certChains) {
+                count += chain.length;
+            }
+
+            // Create new array and copy all the certs into it.
+            Certificate[] certs = new Certificate[count];
+            int i = 0;
+            for (Certificate[] chain : certChains) {
+                System.arraycopy(chain, 0, certs, i, chain.length);
+                i += chain.length;
+            }
+
+            return certs;
+        }
+
+        return null;
+    }
+
+    public InputStream getInputStream(ZipEntry ze) {
+        final InputStream is = getZipInputStream(ze);
+
+        if (isSigned) {
+            StrictJarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
+            if (entry == null) {
+                return is;
+            }
+
+            return new JarFileInputStream(is, ze.getSize(), entry);
+        }
+
+        return is;
+    }
+
+    public void close() throws IOException {
+        if (!closed) {
+            if (guard != null) {
+                guard.close();
+            }
+
+            nativeClose(nativeHandle);
+            IoUtils.closeQuietly(fd);
+            closed = true;
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (guard != null) {
+                guard.warnIfOpen();
+            }
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private InputStream getZipInputStream(ZipEntry ze) {
+        if (ze.getMethod() == ZipEntry.STORED) {
+            return new FDStream(fd, ze.getDataOffset(),
+                    ze.getDataOffset() + ze.getSize());
+        } else {
+            final FDStream wrapped = new FDStream(
+                    fd, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
+
+            int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
+            return new ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
+        }
+    }
+
+    static final class EntryIterator implements Iterator<ZipEntry> {
+        private final long iterationHandle;
+        private ZipEntry nextEntry;
+
+        EntryIterator(long nativeHandle, String prefix) throws IOException {
+            iterationHandle = nativeStartIteration(nativeHandle, prefix);
+        }
+
+        public ZipEntry next() {
+            if (nextEntry != null) {
+                final ZipEntry ze = nextEntry;
+                nextEntry = null;
+                return ze;
+            }
+
+            return nativeNextEntry(iterationHandle);
+        }
+
+        public boolean hasNext() {
+            if (nextEntry != null) {
+                return true;
+            }
+
+            final ZipEntry ze = nativeNextEntry(iterationHandle);
+            if (ze == null) {
+                return false;
+            }
+
+            nextEntry = ze;
+            return true;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private HashMap<String, byte[]> getMetaEntries() throws IOException {
+        HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
+
+        Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/");
+        while (entryIterator.hasNext()) {
+            final ZipEntry entry = entryIterator.next();
+            metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry)));
+        }
+
+        return metaEntries;
+    }
+
+    static final class JarFileInputStream extends FilterInputStream {
+        private final StrictJarVerifier.VerifierEntry entry;
+
+        private long count;
+        private boolean done = false;
+
+        JarFileInputStream(InputStream is, long size, StrictJarVerifier.VerifierEntry e) {
+            super(is);
+            entry = e;
+
+            count = size;
+        }
+
+        @Override
+        public int read() throws IOException {
+            if (done) {
+                return -1;
+            }
+            if (count > 0) {
+                int r = super.read();
+                if (r != -1) {
+                    entry.write(r);
+                    count--;
+                } else {
+                    count = 0;
+                }
+                if (count == 0) {
+                    done = true;
+                    entry.verify();
+                }
+                return r;
+            } else {
+                done = true;
+                entry.verify();
+                return -1;
+            }
+        }
+
+        @Override
+        public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+            if (done) {
+                return -1;
+            }
+            if (count > 0) {
+                int r = super.read(buffer, byteOffset, byteCount);
+                if (r != -1) {
+                    int size = r;
+                    if (count < size) {
+                        size = (int) count;
+                    }
+                    entry.write(buffer, byteOffset, size);
+                    count -= size;
+                } else {
+                    count = 0;
+                }
+                if (count == 0) {
+                    done = true;
+                    entry.verify();
+                }
+                return r;
+            } else {
+                done = true;
+                entry.verify();
+                return -1;
+            }
+        }
+
+        @Override
+        public int available() throws IOException {
+            if (done) {
+                return 0;
+            }
+            return super.available();
+        }
+
+        @Override
+        public long skip(long byteCount) throws IOException {
+            return Streams.skipByReading(this, byteCount);
+        }
+    }
+
+    /** @hide */
+    public static class ZipInflaterInputStream extends InflaterInputStream {
+        private final ZipEntry entry;
+        private long bytesRead = 0;
+        private boolean closed;
+
+        public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) {
+            super(is, inf, bsize);
+            this.entry = entry;
+        }
+
+        @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+            final int i;
+            try {
+                i = super.read(buffer, byteOffset, byteCount);
+            } catch (IOException e) {
+                throw new IOException("Error reading data for " + entry.getName() + " near offset "
+                        + bytesRead, e);
+            }
+            if (i == -1) {
+                if (entry.getSize() != bytesRead) {
+                    throw new IOException("Size mismatch on inflated file: " + bytesRead + " vs "
+                            + entry.getSize());
+                }
+            } else {
+                bytesRead += i;
+            }
+            return i;
+        }
+
+        @Override public int available() throws IOException {
+            if (closed) {
+                // Our superclass will throw an exception, but there's a jtreg test that
+                // explicitly checks that the InputStream returned from ZipFile.getInputStream
+                // returns 0 even when closed.
+                return 0;
+            }
+            return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead);
+        }
+
+        @Override
+        public void close() throws IOException {
+            super.close();
+            closed = true;
+        }
+    }
+
+    /**
+     * Wrap a stream around a FileDescriptor.  The file descriptor is shared
+     * among all streams returned by getInputStream(), so we have to synchronize
+     * access to it.  (We can optimize this by adding buffering here to reduce
+     * collisions.)
+     *
+     * <p>We could support mark/reset, but we don't currently need them.
+     *
+     * @hide
+     */
+    public static class FDStream extends InputStream {
+        private final FileDescriptor fd;
+        private long endOffset;
+        private long offset;
+
+        public FDStream(FileDescriptor fd, long initialOffset, long endOffset) {
+            this.fd = fd;
+            offset = initialOffset;
+            this.endOffset = endOffset;
+        }
+
+        @Override public int available() throws IOException {
+            return (offset < endOffset ? 1 : 0);
+        }
+
+        @Override public int read() throws IOException {
+            return Streams.readSingleByte(this);
+        }
+
+        @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+            synchronized (this.fd) {
+                final long length = endOffset - offset;
+                if (byteCount > length) {
+                    byteCount = (int) length;
+                }
+                try {
+                    Os.lseek(fd, offset, OsConstants.SEEK_SET);
+                } catch (ErrnoException e) {
+                    throw new IOException(e);
+                }
+                int count = IoBridge.read(fd, buffer, byteOffset, byteCount);
+                if (count > 0) {
+                    offset += count;
+                    return count;
+                } else {
+                    return -1;
+                }
+            }
+        }
+
+        @Override public long skip(long byteCount) throws IOException {
+            if (byteCount > endOffset - offset) {
+                byteCount = endOffset - offset;
+            }
+            offset += byteCount;
+            return byteCount;
+        }
+    }
+
+    private static native long nativeOpenJarFile(String name, int fd)
+            throws IOException;
+    private static native long nativeStartIteration(long nativeHandle, String prefix);
+    private static native ZipEntry nativeNextEntry(long iterationHandle);
+    private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
+    private static native void nativeClose(long nativeHandle);
+}
diff --git a/android/util/jar/StrictJarManifest.java b/android/util/jar/StrictJarManifest.java
new file mode 100644
index 0000000..faec099
--- /dev/null
+++ b/android/util/jar/StrictJarManifest.java
@@ -0,0 +1,318 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.jar.Attributes;
+import libcore.io.Streams;
+
+/**
+ * The {@code StrictJarManifest} class is used to obtain attribute information for a
+ * {@code StrictJarFile} and its entries.
+ *
+ * @hide
+ */
+public class StrictJarManifest implements Cloneable {
+    static final int LINE_LENGTH_LIMIT = 72;
+
+    private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
+
+    private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
+
+    /** The attribute name "Name". */
+    static final Attributes.Name ATTRIBUTE_NAME_NAME = new Attributes.Name("Name");
+
+    private final Attributes mainAttributes;
+    private final HashMap<String, Attributes> entries;
+
+    static final class Chunk {
+        final int start;
+        final int end;
+
+        Chunk(int start, int end) {
+            this.start = start;
+            this.end = end;
+        }
+    }
+
+    private HashMap<String, Chunk> chunks;
+
+    /**
+     * The end of the main attributes section in the manifest is needed in
+     * verification.
+     */
+    private int mainEnd;
+
+    /**
+     * Creates a new {@code StrictJarManifest} instance.
+     */
+    public StrictJarManifest() {
+        entries = new HashMap<String, Attributes>();
+        mainAttributes = new Attributes();
+    }
+
+    /**
+     * Creates a new {@code StrictJarManifest} instance using the attributes obtained
+     * from the input stream.
+     *
+     * @param is
+     *            {@code InputStream} to parse for attributes.
+     * @throws IOException
+     *             if an IO error occurs while creating this {@code StrictJarManifest}
+     */
+    public StrictJarManifest(InputStream is) throws IOException {
+        this();
+        read(Streams.readFully(is));
+    }
+
+    /**
+     * Creates a new {@code StrictJarManifest} instance. The new instance will have the
+     * same attributes as those found in the parameter {@code StrictJarManifest}.
+     *
+     * @param man
+     *            {@code StrictJarManifest} instance to obtain attributes from.
+     */
+    @SuppressWarnings("unchecked")
+    public StrictJarManifest(StrictJarManifest man) {
+        mainAttributes = (Attributes) man.mainAttributes.clone();
+        entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
+                .getEntries()).clone();
+    }
+
+    StrictJarManifest(byte[] manifestBytes, boolean readChunks) throws IOException {
+        this();
+        if (readChunks) {
+            chunks = new HashMap<String, Chunk>();
+        }
+        read(manifestBytes);
+    }
+
+    /**
+     * Resets the both the main attributes as well as the entry attributes
+     * associated with this {@code StrictJarManifest}.
+     */
+    public void clear() {
+        entries.clear();
+        mainAttributes.clear();
+    }
+
+    /**
+     * Returns the {@code Attributes} associated with the parameter entry
+     * {@code name}.
+     *
+     * @param name
+     *            the name of the entry to obtain {@code Attributes} from.
+     * @return the Attributes for the entry or {@code null} if the entry does
+     *         not exist.
+     */
+    public Attributes getAttributes(String name) {
+        return getEntries().get(name);
+    }
+
+    /**
+     * Returns a map containing the {@code Attributes} for each entry in the
+     * {@code StrictJarManifest}.
+     *
+     * @return the map of entry attributes.
+     */
+    public Map<String, Attributes> getEntries() {
+        return entries;
+    }
+
+    /**
+     * Returns the main {@code Attributes} of the {@code JarFile}.
+     *
+     * @return main {@code Attributes} associated with the source {@code
+     *         JarFile}.
+     */
+    public Attributes getMainAttributes() {
+        return mainAttributes;
+    }
+
+    /**
+     * Creates a copy of this {@code StrictJarManifest}. The returned {@code StrictJarManifest}
+     * will equal the {@code StrictJarManifest} from which it was cloned.
+     *
+     * @return a copy of this instance.
+     */
+    @Override
+    public Object clone() {
+        return new StrictJarManifest(this);
+    }
+
+    /**
+     * Writes this {@code StrictJarManifest}'s name/attributes pairs to the given {@code OutputStream}.
+     * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
+     * calling this method, or no attributes will be written.
+     *
+     * @throws IOException
+     *             If an error occurs writing the {@code StrictJarManifest}.
+     */
+    public void write(OutputStream os) throws IOException {
+        write(this, os);
+    }
+
+    /**
+     * Merges name/attribute pairs read from the input stream {@code is} into this manifest.
+     *
+     * @param is
+     *            The {@code InputStream} to read from.
+     * @throws IOException
+     *             If an error occurs reading the manifest.
+     */
+    public void read(InputStream is) throws IOException {
+        read(Streams.readFullyNoClose(is));
+    }
+
+    private void read(byte[] buf) throws IOException {
+        if (buf.length == 0) {
+            return;
+        }
+
+        StrictJarManifestReader im = new StrictJarManifestReader(buf, mainAttributes);
+        mainEnd = im.getEndOfMainSection();
+        im.readEntries(entries, chunks);
+    }
+
+    /**
+     * Returns the hash code for this instance.
+     *
+     * @return this {@code StrictJarManifest}'s hashCode.
+     */
+    @Override
+    public int hashCode() {
+        return mainAttributes.hashCode() ^ getEntries().hashCode();
+    }
+
+    /**
+     * Determines if the receiver is equal to the parameter object. Two {@code
+     * StrictJarManifest}s are equal if they have identical main attributes as well as
+     * identical entry attributes.
+     *
+     * @param o
+     *            the object to compare against.
+     * @return {@code true} if the manifests are equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) {
+            return false;
+        }
+        if (o.getClass() != this.getClass()) {
+            return false;
+        }
+        if (!mainAttributes.equals(((StrictJarManifest) o).mainAttributes)) {
+            return false;
+        }
+        return getEntries().equals(((StrictJarManifest) o).getEntries());
+    }
+
+    Chunk getChunk(String name) {
+        return chunks.get(name);
+    }
+
+    void removeChunks() {
+        chunks = null;
+    }
+
+    int getMainAttributesEnd() {
+        return mainEnd;
+    }
+
+    /**
+     * Writes out the attribute information of the specified manifest to the
+     * specified {@code OutputStream}
+     *
+     * @param manifest
+     *            the manifest to write out.
+     * @param out
+     *            The {@code OutputStream} to write to.
+     * @throws IOException
+     *             If an error occurs writing the {@code StrictJarManifest}.
+     */
+    static void write(StrictJarManifest manifest, OutputStream out) throws IOException {
+        CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+        ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
+
+        Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
+        String version = manifest.mainAttributes.getValue(versionName);
+        if (version == null) {
+            versionName = Attributes.Name.SIGNATURE_VERSION;
+            version = manifest.mainAttributes.getValue(versionName);
+        }
+        if (version != null) {
+            writeEntry(out, versionName, version, encoder, buffer);
+            Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
+            while (entries.hasNext()) {
+                Attributes.Name name = (Attributes.Name) entries.next();
+                if (!name.equals(versionName)) {
+                    writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
+                }
+            }
+        }
+        out.write(LINE_SEPARATOR);
+        Iterator<String> i = manifest.getEntries().keySet().iterator();
+        while (i.hasNext()) {
+            String key = i.next();
+            writeEntry(out, ATTRIBUTE_NAME_NAME, key, encoder, buffer);
+            Attributes attributes = manifest.entries.get(key);
+            Iterator<?> entries = attributes.keySet().iterator();
+            while (entries.hasNext()) {
+                Attributes.Name name = (Attributes.Name) entries.next();
+                writeEntry(out, name, attributes.getValue(name), encoder, buffer);
+            }
+            out.write(LINE_SEPARATOR);
+        }
+    }
+
+    private static void writeEntry(OutputStream os, Attributes.Name name,
+            String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
+        String nameString = name.toString();
+        os.write(nameString.getBytes(StandardCharsets.US_ASCII));
+        os.write(VALUE_SEPARATOR);
+
+        encoder.reset();
+        bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
+
+        CharBuffer cBuf = CharBuffer.wrap(value);
+
+        while (true) {
+            CoderResult r = encoder.encode(cBuf, bBuf, true);
+            if (CoderResult.UNDERFLOW == r) {
+                r = encoder.flush(bBuf);
+            }
+            os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
+            os.write(LINE_SEPARATOR);
+            if (CoderResult.UNDERFLOW == r) {
+                break;
+            }
+            os.write(' ');
+            bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
+        }
+    }
+}
diff --git a/android/util/jar/StrictJarManifestReader.java b/android/util/jar/StrictJarManifestReader.java
new file mode 100644
index 0000000..b17abc8
--- /dev/null
+++ b/android/util/jar/StrictJarManifestReader.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jar;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+
+/**
+ * Reads a JAR file manifest. The specification is here:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html
+ */
+class StrictJarManifestReader {
+    // There are relatively few unique attribute names,
+    // but a manifest might have thousands of entries.
+    private final HashMap<String, Attributes.Name> attributeNameCache = new HashMap<String, Attributes.Name>();
+
+    private final ByteArrayOutputStream valueBuffer = new ByteArrayOutputStream(80);
+
+    private final byte[] buf;
+
+    private final int endOfMainSection;
+
+    private int pos;
+
+    private Attributes.Name name;
+
+    private String value;
+
+    private int consecutiveLineBreaks = 0;
+
+    public StrictJarManifestReader(byte[] buf, Attributes main) throws IOException {
+        this.buf = buf;
+        while (readHeader()) {
+            main.put(name, value);
+        }
+        this.endOfMainSection = pos;
+    }
+
+    public void readEntries(Map<String, Attributes> entries, Map<String, StrictJarManifest.Chunk> chunks) throws IOException {
+        int mark = pos;
+        while (readHeader()) {
+            if (!StrictJarManifest.ATTRIBUTE_NAME_NAME.equals(name)) {
+                throw new IOException("Entry is not named");
+            }
+            String entryNameValue = value;
+
+            Attributes entry = entries.get(entryNameValue);
+            if (entry == null) {
+                entry = new Attributes(12);
+            }
+
+            while (readHeader()) {
+                entry.put(name, value);
+            }
+
+            if (chunks != null) {
+                if (chunks.get(entryNameValue) != null) {
+                    // TODO A bug: there might be several verification chunks for
+                    // the same name. I believe they should be used to update
+                    // signature in order of appearance; there are two ways to fix
+                    // this: either use a list of chunks, or decide on used
+                    // signature algorithm in advance and reread the chunks while
+                    // updating the signature; for now a defensive error is thrown
+                    throw new IOException("A jar verifier does not support more than one entry with the same name");
+                }
+                chunks.put(entryNameValue, new StrictJarManifest.Chunk(mark, pos));
+                mark = pos;
+            }
+
+            entries.put(entryNameValue, entry);
+        }
+    }
+
+    public int getEndOfMainSection() {
+        return endOfMainSection;
+    }
+
+    /**
+     * Read a single line from the manifest buffer.
+     */
+    private boolean readHeader() throws IOException {
+        if (consecutiveLineBreaks > 1) {
+            // break a section on an empty line
+            consecutiveLineBreaks = 0;
+            return false;
+        }
+        readName();
+        consecutiveLineBreaks = 0;
+        readValue();
+        // if the last line break is missed, the line
+        // is ignored by the reference implementation
+        return consecutiveLineBreaks > 0;
+    }
+
+    private void readName() throws IOException {
+        int mark = pos;
+
+        while (pos < buf.length) {
+            if (buf[pos++] != ':') {
+                continue;
+            }
+
+            String nameString = new String(buf, mark, pos - mark - 1, StandardCharsets.US_ASCII);
+
+            if (buf[pos++] != ' ') {
+                throw new IOException(String.format("Invalid value for attribute '%s'", nameString));
+            }
+
+            try {
+                name = attributeNameCache.get(nameString);
+                if (name == null) {
+                    name = new Attributes.Name(nameString);
+                    attributeNameCache.put(nameString, name);
+                }
+            } catch (IllegalArgumentException e) {
+                // new Attributes.Name() throws IllegalArgumentException but we declare IOException
+                throw new IOException(e.getMessage());
+            }
+            return;
+        }
+    }
+
+    private void readValue() throws IOException {
+        boolean lastCr = false;
+        int mark = pos;
+        int last = pos;
+        valueBuffer.reset();
+        while (pos < buf.length) {
+            byte next = buf[pos++];
+            switch (next) {
+            case 0:
+                throw new IOException("NUL character in a manifest");
+            case '\n':
+                if (lastCr) {
+                    lastCr = false;
+                } else {
+                    consecutiveLineBreaks++;
+                }
+                continue;
+            case '\r':
+                lastCr = true;
+                consecutiveLineBreaks++;
+                continue;
+            case ' ':
+                if (consecutiveLineBreaks == 1) {
+                    valueBuffer.write(buf, mark, last - mark);
+                    mark = pos;
+                    consecutiveLineBreaks = 0;
+                    continue;
+                }
+            }
+
+            if (consecutiveLineBreaks >= 1) {
+                pos--;
+                break;
+            }
+            last = pos;
+        }
+
+        valueBuffer.write(buf, mark, last - mark);
+        // A bit frustrating that that Charset.forName will be called
+        // again.
+        value = valueBuffer.toString(StandardCharsets.UTF_8.name());
+    }
+}
diff --git a/android/util/jar/StrictJarVerifier.java b/android/util/jar/StrictJarVerifier.java
new file mode 100644
index 0000000..4525490
--- /dev/null
+++ b/android/util/jar/StrictJarVerifier.java
@@ -0,0 +1,548 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jar;
+
+import android.util.apk.ApkSignatureSchemeV2Verifier;
+import android.util.apk.ApkSignatureSchemeV3Verifier;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+
+import sun.security.jca.Providers;
+import sun.security.pkcs.PKCS7;
+import sun.security.pkcs.SignerInfo;
+
+/**
+ * Non-public class used by {@link JarFile} and {@link JarInputStream} to manage
+ * the verification of signed JARs. {@code JarFile} and {@code JarInputStream}
+ * objects are expected to have a {@code JarVerifier} instance member which
+ * can be used to carry out the tasks associated with verifying a signed JAR.
+ * These tasks would typically include:
+ * <ul>
+ * <li>verification of all signed signature files
+ * <li>confirmation that all signed data was signed only by the party or parties
+ * specified in the signature block data
+ * <li>verification that the contents of all signature files (i.e. {@code .SF}
+ * files) agree with the JAR entries information found in the JAR manifest.
+ * </ul>
+ */
+class StrictJarVerifier {
+    /**
+     * {@code .SF} file header section attribute indicating that the APK is signed not just with
+     * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
+     * facilitates v2 signature stripping detection.
+     *
+     * <p>The attribute contains a comma-separated set of signature scheme IDs.
+     */
+    private static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
+
+    /**
+     * List of accepted digest algorithms. This list is in order from most
+     * preferred to least preferred.
+     */
+    private static final String[] DIGEST_ALGORITHMS = new String[] {
+        "SHA-512",
+        "SHA-384",
+        "SHA-256",
+        "SHA1",
+    };
+
+    private final String jarName;
+    private final StrictJarManifest manifest;
+    private final HashMap<String, byte[]> metaEntries;
+    private final int mainAttributesEnd;
+    private final boolean signatureSchemeRollbackProtectionsEnforced;
+
+    private final Hashtable<String, HashMap<String, Attributes>> signatures =
+            new Hashtable<String, HashMap<String, Attributes>>(5);
+
+    private final Hashtable<String, Certificate[]> certificates =
+            new Hashtable<String, Certificate[]>(5);
+
+    private final Hashtable<String, Certificate[][]> verifiedEntries =
+            new Hashtable<String, Certificate[][]>();
+
+    /**
+     * Stores and a hash and a message digest and verifies that massage digest
+     * matches the hash.
+     */
+    static class VerifierEntry extends OutputStream {
+
+        private final String name;
+
+        private final MessageDigest digest;
+
+        private final byte[] hash;
+
+        private final Certificate[][] certChains;
+
+        private final Hashtable<String, Certificate[][]> verifiedEntries;
+
+        VerifierEntry(String name, MessageDigest digest, byte[] hash,
+                Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) {
+            this.name = name;
+            this.digest = digest;
+            this.hash = hash;
+            this.certChains = certChains;
+            this.verifiedEntries = verifedEntries;
+        }
+
+        /**
+         * Updates a digest with one byte.
+         */
+        @Override
+        public void write(int value) {
+            digest.update((byte) value);
+        }
+
+        /**
+         * Updates a digest with byte array.
+         */
+        @Override
+        public void write(byte[] buf, int off, int nbytes) {
+            digest.update(buf, off, nbytes);
+        }
+
+        /**
+         * Verifies that the digests stored in the manifest match the decrypted
+         * digests from the .SF file. This indicates the validity of the
+         * signing, not the integrity of the file, as its digest must be
+         * calculated and verified when its contents are read.
+         *
+         * @throws SecurityException
+         *             if the digest value stored in the manifest does <i>not</i>
+         *             agree with the decrypted digest as recovered from the
+         *             <code>.SF</code> file.
+         */
+        void verify() {
+            byte[] d = digest.digest();
+            if (!verifyMessageDigest(d, hash)) {
+                throw invalidDigest(JarFile.MANIFEST_NAME, name, name);
+            }
+            verifiedEntries.put(name, certChains);
+        }
+    }
+
+    private static SecurityException invalidDigest(String signatureFile, String name,
+            String jarName) {
+        throw new SecurityException(signatureFile + " has invalid digest for " + name +
+                " in " + jarName);
+    }
+
+    private static SecurityException failedVerification(String jarName, String signatureFile) {
+        throw new SecurityException(jarName + " failed verification of " + signatureFile);
+    }
+
+    private static SecurityException failedVerification(String jarName, String signatureFile,
+                                                      Throwable e) {
+        throw new SecurityException(jarName + " failed verification of " + signatureFile, e);
+    }
+
+
+    /**
+     * Constructs and returns a new instance of {@code JarVerifier}.
+     *
+     * @param name
+     *            the name of the JAR file being verified.
+     *
+     * @param signatureSchemeRollbackProtectionsEnforced {@code true} to enforce protections against
+     *        stripping newer signature schemes (e.g., APK Signature Scheme v2) from the file, or
+     *        {@code false} to ignore any such protections.
+     */
+    StrictJarVerifier(String name, StrictJarManifest manifest,
+        HashMap<String, byte[]> metaEntries, boolean signatureSchemeRollbackProtectionsEnforced) {
+        jarName = name;
+        this.manifest = manifest;
+        this.metaEntries = metaEntries;
+        this.mainAttributesEnd = manifest.getMainAttributesEnd();
+        this.signatureSchemeRollbackProtectionsEnforced =
+                signatureSchemeRollbackProtectionsEnforced;
+    }
+
+    /**
+     * Invoked for each new JAR entry read operation from the input
+     * stream. This method constructs and returns a new {@link VerifierEntry}
+     * which contains the certificates used to sign the entry and its hash value
+     * as specified in the JAR MANIFEST format.
+     *
+     * @param name
+     *            the name of an entry in a JAR file which is <b>not</b> in the
+     *            {@code META-INF} directory.
+     * @return a new instance of {@link VerifierEntry} which can be used by
+     *         callers as an {@link OutputStream}.
+     */
+    VerifierEntry initEntry(String name) {
+        // If no manifest is present by the time an entry is found,
+        // verification cannot occur. If no signature files have
+        // been found, do not verify.
+        if (manifest == null || signatures.isEmpty()) {
+            return null;
+        }
+
+        Attributes attributes = manifest.getAttributes(name);
+        // entry has no digest
+        if (attributes == null) {
+            return null;
+        }
+
+        ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();
+        Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<String, HashMap<String, Attributes>> entry = it.next();
+            HashMap<String, Attributes> hm = entry.getValue();
+            if (hm.get(name) != null) {
+                // Found an entry for entry name in .SF file
+                String signatureFile = entry.getKey();
+                Certificate[] certChain = certificates.get(signatureFile);
+                if (certChain != null) {
+                    certChains.add(certChain);
+                }
+            }
+        }
+
+        // entry is not signed
+        if (certChains.isEmpty()) {
+            return null;
+        }
+        Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]);
+
+        for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+            final String algorithm = DIGEST_ALGORITHMS[i];
+            final String hash = attributes.getValue(algorithm + "-Digest");
+            if (hash == null) {
+                continue;
+            }
+            byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+
+            try {
+                return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,
+                        certChainsArray, verifiedEntries);
+            } catch (NoSuchAlgorithmException ignored) {
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Add a new meta entry to the internal collection of data held on each JAR
+     * entry in the {@code META-INF} directory including the manifest
+     * file itself. Files associated with the signing of a JAR would also be
+     * added to this collection.
+     *
+     * @param name
+     *            the name of the file located in the {@code META-INF}
+     *            directory.
+     * @param buf
+     *            the file bytes for the file called {@code name}.
+     * @see #removeMetaEntries()
+     */
+    void addMetaEntry(String name, byte[] buf) {
+        metaEntries.put(name.toUpperCase(Locale.US), buf);
+    }
+
+    /**
+     * If the associated JAR file is signed, check on the validity of all of the
+     * known signatures.
+     *
+     * @return {@code true} if the associated JAR is signed and an internal
+     *         check verifies the validity of the signature(s). {@code false} if
+     *         the associated JAR file has no entries at all in its {@code
+     *         META-INF} directory. This situation is indicative of an invalid
+     *         JAR file.
+     *         <p>
+     *         Will also return {@code true} if the JAR file is <i>not</i>
+     *         signed.
+     * @throws SecurityException
+     *             if the JAR file is signed and it is determined that a
+     *             signature block file contains an invalid signature for the
+     *             corresponding signature file.
+     */
+    synchronized boolean readCertificates() {
+        if (metaEntries.isEmpty()) {
+            return false;
+        }
+
+        Iterator<String> it = metaEntries.keySet().iterator();
+        while (it.hasNext()) {
+            String key = it.next();
+            if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
+                verifyCertificate(key);
+                it.remove();
+            }
+        }
+        return true;
+    }
+
+   /**
+     * Verifies that the signature computed from {@code sfBytes} matches
+     * that specified in {@code blockBytes} (which is a PKCS7 block). Returns
+     * certificates listed in the PKCS7 block. Throws a {@code GeneralSecurityException}
+     * if something goes wrong during verification.
+     */
+    static Certificate[] verifyBytes(byte[] blockBytes, byte[] sfBytes)
+        throws GeneralSecurityException {
+
+        Object obj = null;
+        try {
+
+            obj = Providers.startJarVerification();
+            PKCS7 block = new PKCS7(blockBytes);
+            SignerInfo[] verifiedSignerInfos = block.verify(sfBytes);
+            if ((verifiedSignerInfos == null) || (verifiedSignerInfos.length == 0)) {
+                throw new GeneralSecurityException(
+                        "Failed to verify signature: no verified SignerInfos");
+            }
+            // Ignore any SignerInfo other than the first one, to be compatible with older Android
+            // platforms which have been doing this for years. See
+            // libcore/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
+            // verifySignature method of older platforms.
+            SignerInfo verifiedSignerInfo = verifiedSignerInfos[0];
+            List<X509Certificate> verifiedSignerCertChain =
+                    verifiedSignerInfo.getCertificateChain(block);
+            if (verifiedSignerCertChain == null) {
+                // Should never happen
+                throw new GeneralSecurityException(
+                    "Failed to find verified SignerInfo certificate chain");
+            } else if (verifiedSignerCertChain.isEmpty()) {
+                // Should never happen
+                throw new GeneralSecurityException(
+                    "Verified SignerInfo certificate chain is emtpy");
+            }
+            return verifiedSignerCertChain.toArray(
+                    new X509Certificate[verifiedSignerCertChain.size()]);
+        } catch (IOException e) {
+            throw new GeneralSecurityException("IO exception verifying jar cert", e);
+        } finally {
+            Providers.stopJarVerification(obj);
+        }
+    }
+
+    /**
+     * @param certFile
+     */
+    private void verifyCertificate(String certFile) {
+        // Found Digital Sig, .SF should already have been read
+        String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
+        byte[] sfBytes = metaEntries.get(signatureFile);
+        if (sfBytes == null) {
+            return;
+        }
+
+        byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);
+        // Manifest entry is required for any verifications.
+        if (manifestBytes == null) {
+            return;
+        }
+
+        byte[] sBlockBytes = metaEntries.get(certFile);
+        try {
+            Certificate[] signerCertChain = verifyBytes(sBlockBytes, sfBytes);
+            if (signerCertChain != null) {
+                certificates.put(signatureFile, signerCertChain);
+            }
+        } catch (GeneralSecurityException e) {
+          throw failedVerification(jarName, signatureFile, e);
+        }
+
+        // Verify manifest hash in .sf file
+        Attributes attributes = new Attributes();
+        HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
+        try {
+            StrictJarManifestReader im = new StrictJarManifestReader(sfBytes, attributes);
+            im.readEntries(entries, null);
+        } catch (IOException e) {
+            return;
+        }
+
+        // If requested, check whether a newer APK Signature Scheme signature was stripped.
+        if (signatureSchemeRollbackProtectionsEnforced) {
+            String apkSignatureSchemeIdList =
+                    attributes.getValue(SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
+            if (apkSignatureSchemeIdList != null) {
+                // This field contains a comma-separated list of APK signature scheme IDs which
+                // were used to sign this APK. If an ID is known to us, it means signatures of that
+                // scheme were stripped from the APK because otherwise we wouldn't have fallen back
+                // to verifying the APK using the JAR signature scheme.
+                boolean v2SignatureGenerated = false;
+                boolean v3SignatureGenerated = false;
+                StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ",");
+                while (tokenizer.hasMoreTokens()) {
+                    String idText = tokenizer.nextToken().trim();
+                    if (idText.isEmpty()) {
+                        continue;
+                    }
+                    int id;
+                    try {
+                        id = Integer.parseInt(idText);
+                    } catch (Exception ignored) {
+                        continue;
+                    }
+                    if (id == ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+                        // This APK was supposed to be signed with APK Signature Scheme v2 but no
+                        // such signature was found.
+                        v2SignatureGenerated = true;
+                        break;
+                    }
+                    if (id == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+                        // This APK was supposed to be signed with APK Signature Scheme v3 but no
+                        // such signature was found.
+                        v3SignatureGenerated = true;
+                        break;
+                    }
+                }
+
+                if (v2SignatureGenerated) {
+                    throw new SecurityException(signatureFile + " indicates " + jarName
+                            + " is signed using APK Signature Scheme v2, but no such signature was"
+                            + " found. Signature stripped?");
+                }
+                if (v3SignatureGenerated) {
+                    throw new SecurityException(signatureFile + " indicates " + jarName
+                            + " is signed using APK Signature Scheme v3, but no such signature was"
+                            + " found. Signature stripped?");
+                }
+            }
+        }
+
+        // Do we actually have any signatures to look at?
+        if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
+            return;
+        }
+
+        boolean createdBySigntool = false;
+        String createdBy = attributes.getValue("Created-By");
+        if (createdBy != null) {
+            createdBySigntool = createdBy.indexOf("signtool") != -1;
+        }
+
+        // Use .SF to verify the mainAttributes of the manifest
+        // If there is no -Digest-Manifest-Main-Attributes entry in .SF
+        // file, such as those created before java 1.5, then we ignore
+        // such verification.
+        if (mainAttributesEnd > 0 && !createdBySigntool) {
+            String digestAttribute = "-Digest-Manifest-Main-Attributes";
+            if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {
+                throw failedVerification(jarName, signatureFile);
+            }
+        }
+
+        // Use .SF to verify the whole manifest.
+        String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
+        if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {
+            Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<String, Attributes> entry = it.next();
+                StrictJarManifest.Chunk chunk = manifest.getChunk(entry.getKey());
+                if (chunk == null) {
+                    return;
+                }
+                if (!verify(entry.getValue(), "-Digest", manifestBytes,
+                        chunk.start, chunk.end, createdBySigntool, false)) {
+                    throw invalidDigest(signatureFile, entry.getKey(), jarName);
+                }
+            }
+        }
+        metaEntries.put(signatureFile, null);
+        signatures.put(signatureFile, entries);
+    }
+
+    /**
+     * Returns a <code>boolean</code> indication of whether or not the
+     * associated jar file is signed.
+     *
+     * @return {@code true} if the JAR is signed, {@code false}
+     *         otherwise.
+     */
+    boolean isSignedJar() {
+        return certificates.size() > 0;
+    }
+
+    private boolean verify(Attributes attributes, String entry, byte[] data,
+            int start, int end, boolean ignoreSecondEndline, boolean ignorable) {
+        for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+            String algorithm = DIGEST_ALGORITHMS[i];
+            String hash = attributes.getValue(algorithm + entry);
+            if (hash == null) {
+                continue;
+            }
+
+            MessageDigest md;
+            try {
+                md = MessageDigest.getInstance(algorithm);
+            } catch (NoSuchAlgorithmException e) {
+                continue;
+            }
+            if (ignoreSecondEndline && data[end - 1] == '\n' && data[end - 2] == '\n') {
+                md.update(data, start, end - 1 - start);
+            } else {
+                md.update(data, start, end - start);
+            }
+            byte[] b = md.digest();
+            byte[] encodedHashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+            return verifyMessageDigest(b, encodedHashBytes);
+        }
+        return ignorable;
+    }
+
+    private static boolean verifyMessageDigest(byte[] expected, byte[] encodedActual) {
+        byte[] actual;
+        try {
+            actual = java.util.Base64.getDecoder().decode(encodedActual);
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+        return MessageDigest.isEqual(expected, actual);
+    }
+
+    /**
+     * Returns all of the {@link java.security.cert.Certificate} chains that
+     * were used to verify the signature on the JAR entry called
+     * {@code name}. Callers must not modify the returned arrays.
+     *
+     * @param name
+     *            the name of a JAR entry.
+     * @return an array of {@link java.security.cert.Certificate} chains.
+     */
+    Certificate[][] getCertificateChains(String name) {
+        return verifiedEntries.get(name);
+    }
+
+    /**
+     * Remove all entries from the internal collection of data held about each
+     * JAR entry in the {@code META-INF} directory.
+     */
+    void removeMetaEntries() {
+        metaEntries.clear();
+    }
+}
diff --git a/android/util/perftests/LogPerfTest.java b/android/util/perftests/LogPerfTest.java
new file mode 100644
index 0000000..26cec95
--- /dev/null
+++ b/android/util/perftests/LogPerfTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 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.perftests;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class LogPerfTest {
+
+    private final String[] strings = new String[] {
+            "This is a test log string 1",
+            "This is a test log string 2",
+            "This is a test log string 3",
+            "This is a test log string 4",
+            "This is a test log string 5",
+    };
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testLogPerf() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        int i = 0;
+        while (state.keepRunning()) {
+            Log.d("LogPerfTest", strings[(i++) % strings.length]);
+        }
+    }
+}
diff --git a/android/util/proto/EncodedBuffer.java b/android/util/proto/EncodedBuffer.java
new file mode 100644
index 0000000..56a0bfa
--- /dev/null
+++ b/android/util/proto/EncodedBuffer.java
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) 2012 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.proto;
+
+import android.annotation.TestApi;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * A stream of bytes containing a read pointer and a write pointer,
+ * backed by a set of fixed-size buffers.  There are write functions for the
+ * primitive types stored by protocol buffers, but none of the logic
+ * for tags, inner objects, or any of that.
+ *
+ * Terminology:
+ *      *Pos:       Position in the whole data set (as if it were a single buffer).
+ *      *Index:     Position within a buffer.
+ *      *BufIndex:  Index of a buffer within the mBuffers list
+ * @hide
+ */
+@TestApi
+public final class EncodedBuffer {
+    private static final String TAG = "EncodedBuffer";
+
+    private final ArrayList<byte[]> mBuffers = new ArrayList<byte[]>();
+
+    private final int mChunkSize;
+
+    /**
+     * The number of buffers in mBuffers. Stored separately to avoid the extra
+     * function call to size() everywhere for bounds checking.
+     */
+    private int mBufferCount;
+
+    /**
+     * The buffer we are currently writing to.
+     */
+    private byte[] mWriteBuffer;
+
+    /**
+     * The index into mWriteBuffer that we will write to next.
+     * It may point to the end of the buffer, in which case,
+     * the NEXT write will allocate a new buffer.
+     */
+    private int mWriteIndex;
+
+    /**
+     * The index of mWriteBuffer in mBuffers.
+     */
+    private int mWriteBufIndex;
+
+    /**
+     * The buffer we are currently reading from.
+     */
+    private byte[] mReadBuffer;
+
+    /**
+     * The index of mReadBuffer in mBuffers.
+     */
+    private int mReadBufIndex;
+
+    /**
+     * The index into mReadBuffer that we will read from next.
+     * It may point to the end of the buffer, in which case,
+     * the NEXT read will advance to the next buffer.
+     */
+    private int mReadIndex;
+
+    /**
+     * The amount of data in the last buffer.
+     */
+    private int mReadLimit = -1;
+
+    /**
+     * How much data there is total.
+     */
+    private int mReadableSize = -1;
+
+    public EncodedBuffer() {
+        this(0);
+    }
+
+    /**
+     * Construct an EncodedBuffer object.
+     *
+     * @param chunkSize The size of the buffers to use.  If chunkSize &lt;= 0, a default
+     *                  size will be used instead.
+     */
+    public EncodedBuffer(int chunkSize) {
+        if (chunkSize <= 0) {
+            chunkSize = 8 * 1024;
+        }
+        mChunkSize = chunkSize;
+        mWriteBuffer = new byte[mChunkSize];
+        mBuffers.add(mWriteBuffer);
+        mBufferCount = 1;
+    }
+
+    //
+    // Buffer management.
+    //
+
+    /**
+     * Rewind the read and write pointers, and record how much data was last written.
+     */
+    public void startEditing() {
+        mReadableSize = ((mWriteBufIndex) * mChunkSize) + mWriteIndex;
+        mReadLimit = mWriteIndex;
+
+        mWriteBuffer = mBuffers.get(0);
+        mWriteIndex = 0;
+        mWriteBufIndex = 0;
+
+        mReadBuffer = mWriteBuffer;
+        mReadBufIndex = 0;
+        mReadIndex = 0;
+    }
+
+    /**
+     * Rewind the read pointer. Don't touch the write pointer.
+     */
+    public void rewindRead() {
+        mReadBuffer = mBuffers.get(0);
+        mReadBufIndex = 0;
+        mReadIndex = 0;
+    }
+
+    /**
+     * Only valid after startEditing. Returns -1 before that.
+     */
+    public int getReadableSize() {
+        return mReadableSize;
+    }
+
+    /**
+     * Returns the buffer size
+     * @return the buffer size
+     */
+    public int getSize() {
+        return ((mBufferCount - 1) * mChunkSize) + mWriteIndex;
+    }
+
+    //
+    // Reading from the read position.
+    //
+
+    /**
+     * Only valid after startEditing.
+     */
+    public int getReadPos() {
+        return ((mReadBufIndex) * mChunkSize) + mReadIndex;
+    }
+
+    /**
+     * Skip over _amount_ bytes.
+     */
+    public void skipRead(int amount) {
+        if (amount < 0) {
+            throw new RuntimeException("skipRead with negative amount=" + amount);
+        }
+        if (amount == 0) {
+            return;
+        }
+        if (amount <= mChunkSize - mReadIndex) {
+            mReadIndex += amount;
+        } else {
+            amount -= mChunkSize - mReadIndex;
+            mReadIndex = amount % mChunkSize;
+            if (mReadIndex == 0) {
+                mReadIndex = mChunkSize;
+                mReadBufIndex += (amount / mChunkSize);
+            } else {
+                mReadBufIndex += 1 + (amount / mChunkSize);
+            }
+            mReadBuffer = mBuffers.get(mReadBufIndex);
+        }
+    }
+
+    /**
+     * Read one byte from the stream and advance the read pointer.
+     *
+     * @throws IndexOutOfBoundsException if the read point is past the end of
+     * the buffer or past the read limit previously set by startEditing().
+     */
+    public byte readRawByte() {
+        if (mReadBufIndex > mBufferCount
+                || (mReadBufIndex == mBufferCount - 1 && mReadIndex >= mReadLimit)) {
+            throw new IndexOutOfBoundsException("Trying to read too much data"
+                    + " mReadBufIndex=" + mReadBufIndex + " mBufferCount=" + mBufferCount
+                    + " mReadIndex=" + mReadIndex + " mReadLimit=" + mReadLimit);
+        }
+        if (mReadIndex >= mChunkSize) {
+            mReadBufIndex++;
+            mReadBuffer = mBuffers.get(mReadBufIndex);
+            mReadIndex = 0;
+        }
+        return mReadBuffer[mReadIndex++];
+    }
+
+    /**
+     * Read an unsigned varint. The value will be returend in a java signed long.
+     */
+    public long readRawUnsigned() {
+        int bits = 0;
+        long result = 0;
+        while (true) {
+            final byte b = readRawByte();
+            result |= ((long)(b & 0x7F)) << bits;
+            if ((b & 0x80) == 0) {
+                return result;
+            }
+            bits += 7;
+            if (bits > 64) {
+                throw new ProtoParseException("Varint too long -- " + getDebugString());
+            }
+        }
+    }
+
+    /**
+     * Read 32 little endian bits from the stream.
+     */
+    public int readRawFixed32() {
+        return (readRawByte() & 0x0ff)
+                | ((readRawByte() & 0x0ff) << 8)
+                | ((readRawByte() & 0x0ff) << 16)
+                | ((readRawByte() & 0x0ff) << 24);
+    }
+
+    //
+    // Writing at a the end of the stream.
+    //
+
+    /**
+     * Advance to the next write buffer, allocating it if necessary.
+     *
+     * Must be called immediately <b>before</b> the next write, not after a write,
+     * so that a dangling empty buffer is not created.  Doing so will interfere
+     * with the expectation that mWriteIndex will point past the end of the buffer
+     * until the next read happens.
+     */
+    private void nextWriteBuffer() {
+        mWriteBufIndex++;
+        if (mWriteBufIndex >= mBufferCount) {
+            mWriteBuffer = new byte[mChunkSize];
+            mBuffers.add(mWriteBuffer);
+            mBufferCount++;
+        } else {
+            mWriteBuffer = mBuffers.get(mWriteBufIndex);
+        }
+        mWriteIndex = 0;
+    }
+
+    /**
+     * Write a single byte to the stream.
+     */
+    public void writeRawByte(byte val) {
+        if (mWriteIndex >= mChunkSize) {
+            nextWriteBuffer();
+        }
+        mWriteBuffer[mWriteIndex++] = val;
+    }
+
+    /**
+     * Return how many bytes a 32 bit unsigned varint will take when written to the stream.
+     */
+    public static int getRawVarint32Size(int val) {
+        if ((val & (0xffffffff << 7)) == 0) return 1;
+        if ((val & (0xffffffff << 14)) == 0) return 2;
+        if ((val & (0xffffffff << 21)) == 0) return 3;
+        if ((val & (0xffffffff << 28)) == 0) return 4;
+        return 5;
+    }
+
+    /**
+     * Write an unsigned varint to the stream. A signed value would need to take 10 bytes.
+     *
+     * @param val treated as unsigned.
+     */
+    public void writeRawVarint32(int val) {
+        while (true) {
+            if ((val & ~0x7F) == 0) {
+                writeRawByte((byte)val);
+                return;
+            } else {
+                writeRawByte((byte)((val & 0x7F) | 0x80));
+                val >>>= 7;
+            }
+        }
+    }
+
+    /**
+     * Return how many bytes a 32 bit signed zig zag value will take when written to the stream.
+     */
+    public static int getRawZigZag32Size(int val) {
+        return getRawVarint32Size(zigZag32(val));
+    }
+
+    /**
+     *  Write a zig-zag encoded value.
+     *
+     *  @param val treated as signed
+     */
+    public void writeRawZigZag32(int val) {
+        writeRawVarint32(zigZag32(val));
+    }
+
+    /**
+     * Return how many bytes a 64 bit varint will take when written to the stream.
+     */
+    public static int getRawVarint64Size(long val) {
+        if ((val & (0xffffffffffffffffL << 7)) == 0) return 1;
+        if ((val & (0xffffffffffffffffL << 14)) == 0) return 2;
+        if ((val & (0xffffffffffffffffL << 21)) == 0) return 3;
+        if ((val & (0xffffffffffffffffL << 28)) == 0) return 4;
+        if ((val & (0xffffffffffffffffL << 35)) == 0) return 5;
+        if ((val & (0xffffffffffffffffL << 42)) == 0) return 6;
+        if ((val & (0xffffffffffffffffL << 49)) == 0) return 7;
+        if ((val & (0xffffffffffffffffL << 56)) == 0) return 8;
+        if ((val & (0xffffffffffffffffL << 63)) == 0) return 9;
+        return 10;
+    }
+
+    /**
+     * Write a 64 bit varint to the stream.
+     */
+    public void writeRawVarint64(long val) {
+        while (true) {
+            if ((val & ~0x7FL) == 0) {
+                writeRawByte((byte)val);
+                return;
+            } else {
+                writeRawByte((byte)((val & 0x7F) | 0x80));
+                val >>>= 7;
+            }
+        }
+    }
+
+    /**
+     * Return how many bytes a signed 64 bit zig zag value will take when written to the stream.
+     */
+    public static int getRawZigZag64Size(long val) {
+        return getRawVarint64Size(zigZag64(val));
+    }
+
+    /**
+     * Write a 64 bit signed zig zag value to the stream.
+     */
+    public void writeRawZigZag64(long val) {
+        writeRawVarint64(zigZag64(val));
+    }
+
+    /**
+     * Write 4 little endian bytes to the stream.
+     */
+    public void writeRawFixed32(int val) {
+        writeRawByte((byte)(val));
+        writeRawByte((byte)(val >> 8));
+        writeRawByte((byte)(val >> 16));
+        writeRawByte((byte)(val >> 24));
+    }
+
+    /**
+     * Write 8 little endian bytes to the stream.
+     */
+    public void writeRawFixed64(long val) {
+        writeRawByte((byte)(val));
+        writeRawByte((byte)(val >> 8));
+        writeRawByte((byte)(val >> 16));
+        writeRawByte((byte)(val >> 24));
+        writeRawByte((byte)(val >> 32));
+        writeRawByte((byte)(val >> 40));
+        writeRawByte((byte)(val >> 48));
+        writeRawByte((byte)(val >> 56));
+    }
+
+    /**
+     * Write a buffer to the stream. Writes nothing if val is null or zero-length.
+     */
+    public void writeRawBuffer(byte[] val) {
+        if (val != null && val.length > 0) {
+            writeRawBuffer(val, 0, val.length);
+        }
+    }
+
+    /**
+     * Write part of an array of bytes.
+     */
+    public void writeRawBuffer(byte[] val, int offset, int length) {
+        if (val == null) {
+            return;
+        }
+        // Write up to the amount left in the first chunk to write.
+        int amt = length < (mChunkSize - mWriteIndex) ? length : (mChunkSize - mWriteIndex);
+        if (amt > 0) {
+            System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt);
+            mWriteIndex += amt;
+            length -= amt;
+            offset += amt;
+        }
+        while (length > 0) {
+            // We know we're now at the beginning of a chunk
+            nextWriteBuffer();
+            amt = length < mChunkSize ? length : mChunkSize;
+            System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt);
+            mWriteIndex += amt;
+            length -= amt;
+            offset += amt;
+        }
+    }
+
+    /**
+     * Copies data _size_ bytes of data within this buffer from _srcOffset_
+     * to the current write position. Like memmov but handles the chunked buffer.
+     */
+    public void writeFromThisBuffer(int srcOffset, int size) {
+        if (mReadLimit < 0) {
+            throw new IllegalStateException("writeFromThisBuffer before startEditing");
+        }
+        if (srcOffset < getWritePos()) {
+            throw new IllegalArgumentException("Can only move forward in the buffer --"
+                    + " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString());
+        }
+        if (srcOffset + size > mReadableSize) {
+            throw new IllegalArgumentException("Trying to move more data than there is --"
+                    + " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString());
+        }
+        if (size == 0) {
+            return;
+        }
+        if (srcOffset == ((mWriteBufIndex) * mChunkSize) + mWriteIndex /* write pos */) {
+            // Writing to the same location. Just advance the write pointer.  We already
+            // checked that size is in bounds, so we don't need to do any more range
+            // checking.
+            if (size <= mChunkSize - mWriteIndex) {
+                mWriteIndex += size;
+            } else {
+                size -= mChunkSize - mWriteIndex;
+                mWriteIndex = size % mChunkSize;
+                if (mWriteIndex == 0) {
+                    // Roll it back so nextWriteBuffer can do its job
+                    // on the next call (also makes mBuffers.get() not
+                    // fail if we're at the end).
+                    mWriteIndex = mChunkSize;
+                    mWriteBufIndex += (size / mChunkSize);
+                } else {
+                    mWriteBufIndex += 1 + (size / mChunkSize);
+                }
+                mWriteBuffer = mBuffers.get(mWriteBufIndex);
+            }
+        } else {
+            // Loop through the buffer, copying as much as we can each time.
+            // We already bounds checked so we don't need to do it again here,
+            // and nextWriteBuffer will never allocate.
+            int readBufIndex = srcOffset / mChunkSize;
+            byte[] readBuffer = mBuffers.get(readBufIndex);
+            int readIndex = srcOffset % mChunkSize;
+            while (size > 0) {
+                if (mWriteIndex >= mChunkSize) {
+                    nextWriteBuffer();
+                }
+                if (readIndex >= mChunkSize) {
+                    readBufIndex++;
+                    readBuffer = mBuffers.get(readBufIndex);
+                    readIndex = 0;
+                }
+                final int spaceInWriteBuffer = mChunkSize - mWriteIndex;
+                final int availableInReadBuffer = mChunkSize - readIndex;
+                final int amt = Math.min(size, Math.min(spaceInWriteBuffer, availableInReadBuffer));
+                System.arraycopy(readBuffer, readIndex, mWriteBuffer, mWriteIndex, amt);
+                mWriteIndex += amt;
+                readIndex += amt;
+                size -= amt;
+            }
+        }
+    }
+
+    //
+    // Writing at a particular location.
+    //
+
+    /**
+     * Returns the index into the virtual array of the write pointer.
+     */
+    public int getWritePos() {
+        return ((mWriteBufIndex) * mChunkSize) + mWriteIndex;
+    }
+
+    /**
+     * Resets the write pointer to a virtual location as returned by getWritePos.
+     */
+    public void rewindWriteTo(int writePos) {
+        if (writePos > getWritePos()) {
+            throw new RuntimeException("rewindWriteTo only can go backwards" + writePos);
+        }
+        mWriteBufIndex = writePos / mChunkSize;
+        mWriteIndex = writePos % mChunkSize;
+        if (mWriteIndex == 0 && mWriteBufIndex != 0) {
+            // Roll back so nextWriteBuffer can do its job on the next call
+            // but at the first write we're at 0.
+            mWriteIndex = mChunkSize;
+            mWriteBufIndex--;
+        }
+        mWriteBuffer = mBuffers.get(mWriteBufIndex);
+    }
+
+    /**
+     * Read a 32 bit value from the stream.
+     *
+     * Doesn't touch or affect mWritePos.
+     */
+    public int getRawFixed32At(int pos) {
+        return (0x00ff & (int)mBuffers.get(pos / mChunkSize)[pos % mChunkSize])
+                | ((0x0ff & (int)mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize]) << 8)
+                | ((0x0ff & (int)mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize]) << 16)
+                | ((0x0ff & (int)mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize]) << 24);
+    }
+
+    /**
+     * Overwrite a 32 bit value in the stream.
+     *
+     * Doesn't touch or affect mWritePos.
+     */
+    public void editRawFixed32(int pos, int val) {
+        mBuffers.get(pos / mChunkSize)[pos % mChunkSize] = (byte)(val);
+        mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize] = (byte)(val >> 8);
+        mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize] = (byte)(val >> 16);
+        mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize] = (byte)(val >> 24);
+    }
+
+    //
+    // Zigging and zagging
+    //
+
+    /**
+     * Zig-zag encode a 32 bit value.
+     */
+    private static int zigZag32(int val) {
+        return (val << 1) ^ (val >> 31);
+    }
+
+    /**
+     * Zig-zag encode a 64 bit value.
+     */
+    private static long zigZag64(long val) {
+        return (val << 1) ^ (val >> 63);
+    }
+
+    //
+    // Debugging / testing
+    //
+    // VisibleForTesting
+
+    /**
+     * Get a copy of the first _size_ bytes of data. This is not range
+     * checked, and if the bounds are outside what has been written you will
+     * get garbage and if it is outside the buffers that have been allocated,
+     * you will get an exception.
+     */
+    public byte[] getBytes(int size) {
+        final byte[] result = new byte[size];
+
+        final int bufCount = size / mChunkSize;
+        int bufIndex;
+        int writeIndex = 0;
+
+        for (bufIndex=0; bufIndex<bufCount; bufIndex++) {
+            System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, mChunkSize);
+            writeIndex += mChunkSize;
+        }
+
+        final int lastSize = size - (bufCount * mChunkSize);
+        if (lastSize > 0) {
+            System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, lastSize);
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the number of chunks allocated.
+     */
+    // VisibleForTesting
+    public int getChunkCount() {
+        return mBuffers.size();
+    }
+
+    /**
+     * Get the write position inside the current write chunk.
+     */
+     // VisibleForTesting
+    public int getWriteIndex() {
+        return mWriteIndex;
+    }
+
+    /**
+     * Get the index of the current write chunk in the list of chunks.
+     */
+    // VisibleForTesting
+    public int getWriteBufIndex() {
+        return mWriteBufIndex;
+    }
+
+    /**
+     * Return debugging information about this EncodedBuffer object.
+     */
+    public String getDebugString() {
+        return "EncodedBuffer( mChunkSize=" + mChunkSize + " mBuffers.size=" + mBuffers.size()
+                + " mBufferCount=" + mBufferCount + " mWriteIndex=" + mWriteIndex
+                + " mWriteBufIndex=" + mWriteBufIndex + " mReadBufIndex=" + mReadBufIndex
+                + " mReadIndex=" + mReadIndex + " mReadableSize=" + mReadableSize
+                + " mReadLimit=" + mReadLimit + " )";
+    }
+
+    /**
+     * Print the internal buffer chunks.
+     */
+    public void dumpBuffers(String tag) {
+        final int N = mBuffers.size();
+        int start = 0;
+        for (int i=0; i<N; i++) {
+            start += dumpByteString(tag, "{" + i + "} ", start, mBuffers.get(i));
+        }
+    }
+
+    /**
+     * Print the internal buffer chunks.
+     */
+    public static void dumpByteString(String tag, String prefix, byte[] buf) {
+        dumpByteString(tag, prefix, 0, buf);
+    }
+
+    /**
+     * Print the internal buffer chunks.
+     */
+    private static int dumpByteString(String tag, String prefix, int start, byte[] buf) {
+        StringBuffer sb = new StringBuffer();
+        final int length = buf.length;
+        final int lineLen = 16;
+        int i;
+        for (i=0; i<length; i++) {
+            if (i % lineLen == 0) {
+                if (i != 0) {
+                    Log.d(tag, sb.toString());
+                    sb = new StringBuffer();
+                }
+                sb.append(prefix);
+                sb.append('[');
+                sb.append(start + i);
+                sb.append(']');
+                sb.append(' ');
+            } else {
+                sb.append(' ');
+            }
+            byte b = buf[i];
+            byte c = (byte)((b >> 4) & 0x0f);
+            if (c < 10) {
+                sb.append((char)('0' + c));
+            } else {
+                sb.append((char)('a' - 10 + c));
+            }
+            byte d = (byte)(b & 0x0f);
+            if (d < 10) {
+                sb.append((char)('0' + d));
+            } else {
+                sb.append((char)('a' - 10 + d));
+            }
+        }
+        Log.d(tag, sb.toString());
+        return length;
+    }
+}
diff --git a/android/util/proto/ProtoInputStream.java b/android/util/proto/ProtoInputStream.java
new file mode 100644
index 0000000..cbe3734
--- /dev/null
+++ b/android/util/proto/ProtoInputStream.java
@@ -0,0 +1,993 @@
+/*
+ * Copyright (C) 2018 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.proto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+/**
+ * Class to read to a protobuf stream.
+ *
+ * Each read method takes an ID code from the protoc generated classes
+ * and return a value of the field. To read a nested object, call #start
+ * and then #end when you are done.
+ *
+ * The ID codes have type information embedded into them, so if you call
+ * the incorrect function you will get an IllegalArgumentException.
+ *
+ * nextField will return the field number of the next field, which can be
+ * matched to the protoc generated ID code and used to determine how to
+ * read the next field.
+ *
+ * It is STRONGLY RECOMMENDED to read from the ProtoInputStream with a switch
+ * statement wrapped in a while loop. Additionally, it is worth logging or
+ * storing unexpected fields or ones that do not match the expected wire type
+ *
+ * ex:
+ * void parseFromProto(ProtoInputStream stream) {
+ *     while(stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ *         try {
+ *             switch (stream.getFieldNumber()) {
+ *                 case (int) DummyProto.NAME:
+ *                     mName = stream.readString(DummyProto.NAME);
+ *                     break;
+ *                 case (int) DummyProto.VALUE:
+ *                     mValue = stream.readInt(DummyProto.VALUE);
+ *                     break;
+ *                 default:
+ *                     LOG(TAG, "Unhandled field in proto!\n"
+ *                              + ProtoUtils.currentFieldToString(stream));
+ *             }
+ *         } catch (WireTypeMismatchException wtme) {
+ *             LOG(TAG, "Wire Type mismatch in proto!\n" + ProtoUtils.currentFieldToString(stream));
+ *         }
+ *     }
+ * }
+ *
+ * @hide
+ */
+public final class ProtoInputStream extends ProtoStream {
+
+    public static final int NO_MORE_FIELDS = -1;
+
+    /**
+     * Our stream.  If there is one.
+     */
+    private InputStream mStream;
+
+    /**
+     * The field number of the current field. Will be equal to NO_MORE_FIELDS if end of message is
+     * reached
+     */
+    private int mFieldNumber;
+
+    /**
+     * The wire type of the current field
+     */
+    private int mWireType;
+
+    private static final byte STATE_STARTED_FIELD_READ = 1 << 0;
+    private static final byte STATE_READING_PACKED = 1 << 1;
+    private static final byte STATE_FIELD_MISS = 2 << 1;
+
+    /**
+     * Tracks some boolean states for the proto input stream
+     * bit 0: Started Field Read, true - tag has been read, ready to read field data.
+     * false - field data has been read, reading to start next field.
+     * bit 1: Reading Packed Field, true - currently reading values from a packed field
+     * false - not reading from packed field.
+     */
+    private byte mState = 0;
+
+    /**
+     * Keeps track of the currently read nested Objects, for end object sanity checking and debug
+     */
+    private ArrayList<Long> mExpectedObjectTokenStack = null;
+
+    /**
+     * Current nesting depth of start calls.
+     */
+    private int mDepth = -1;
+
+    /**
+     * Buffer for the to be read data. If mStream is not null, it will be constantly refilled from
+     * the stream.
+     */
+    private byte[] mBuffer;
+
+    private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    /**
+     * Size of the buffer if reading from a stream.
+     */
+    private final int mBufferSize;
+
+    /**
+     * The number of bytes that have been skipped or dropped from the buffer.
+     */
+    private int mDiscardedBytes = 0;
+
+    /**
+     * Current offset in the buffer
+     * mOffset + mDiscardedBytes = current offset in proto binary
+     */
+    private int mOffset = 0;
+
+    /**
+     * Note the offset of the last byte in the buffer. Usually will equal the size of the buffer.
+     * mEnd + mDiscardedBytes = the last known byte offset + 1
+     */
+    private int mEnd = 0;
+
+    /**
+     * Packed repeated fields are not read in one go. mPackedEnd keeps track of where the packed
+     * field ends in the proto binary if current field is packed.
+     */
+    private int mPackedEnd = 0;
+
+    /**
+     * Construct a ProtoInputStream on top of an InputStream to read a proto. Also specify the
+     * number of bytes the ProtoInputStream will buffer from the input stream
+     *
+     * @param stream from which the proto is read
+     */
+    public ProtoInputStream(InputStream stream, int bufferSize) {
+        mStream = stream;
+        if (bufferSize > 0) {
+            mBufferSize = bufferSize;
+        } else {
+            mBufferSize = DEFAULT_BUFFER_SIZE;
+        }
+        mBuffer = new byte[mBufferSize];
+    }
+
+    /**
+     * Construct a ProtoInputStream on top of an InputStream to read a proto
+     *
+     * @param stream from which the proto is read
+     */
+    public ProtoInputStream(InputStream stream) {
+        this(stream, DEFAULT_BUFFER_SIZE);
+    }
+
+    /**
+     * Construct a ProtoInputStream to read a proto directly from a byte array
+     *
+     * @param buffer - the byte array to be parsed
+     */
+    public ProtoInputStream(byte[] buffer) {
+        mBufferSize = buffer.length;
+        mEnd = buffer.length;
+        mBuffer = buffer;
+        mStream = null;
+    }
+
+    /**
+     * Get the field number of the current field.
+     */
+    public int getFieldNumber() {
+        return mFieldNumber;
+    }
+
+    /**
+     * Get the wire type of the current field.
+     *
+     * @return an int that matches one of the ProtoStream WIRE_TYPE_ constants
+     */
+    public int getWireType() {
+        if ((mState & STATE_READING_PACKED) == STATE_READING_PACKED) {
+            // mWireType got overwritten when STATE_READING_PACKED was set. Send length delimited
+            // constant instead
+            return WIRE_TYPE_LENGTH_DELIMITED;
+        }
+        return mWireType;
+    }
+
+    /**
+     * Get the current offset in the proto binary.
+     */
+    public int getOffset() {
+        return mOffset + mDiscardedBytes;
+    }
+
+    /**
+     * Reads the tag of the next field from the stream. If previous field value was not read, its
+     * data will be skipped over.
+     *
+     * @return the field number of the next field
+     * @throws IOException if an I/O error occurs
+     */
+    public int nextField() throws IOException {
+
+        if ((mState & STATE_FIELD_MISS) == STATE_FIELD_MISS) {
+            // Data from the last nextField was not used, reuse the info
+            mState &= ~STATE_FIELD_MISS;
+            return mFieldNumber;
+        }
+        if ((mState & STATE_STARTED_FIELD_READ) == STATE_STARTED_FIELD_READ) {
+            // Field data was not read, skip to the next field
+            skip();
+            mState &= ~STATE_STARTED_FIELD_READ;
+        }
+        if ((mState & STATE_READING_PACKED) == STATE_READING_PACKED) {
+            if (getOffset() < mPackedEnd) {
+                // In the middle of a packed field, return the same tag until last packed value
+                // has been read
+                mState |= STATE_STARTED_FIELD_READ;
+                return mFieldNumber;
+            } else if (getOffset() == mPackedEnd) {
+                // Reached the end of the packed field
+                mState &= ~STATE_READING_PACKED;
+            } else {
+                throw new ProtoParseException(
+                        "Unexpectedly reached end of packed field at offset 0x"
+                                + Integer.toHexString(mPackedEnd)
+                                + dumpDebugData());
+            }
+        }
+
+        if ((mDepth >= 0) && (getOffset() == getOffsetFromToken(
+                mExpectedObjectTokenStack.get(mDepth)))) {
+            // reached end of a embedded message
+            mFieldNumber = NO_MORE_FIELDS;
+        } else {
+            readTag();
+        }
+        return mFieldNumber;
+    }
+
+    /**
+     * Reads the tag of the next field from the stream. If previous field value was not read, its
+     * data will be skipped over. If {@code fieldId} matches the next field ID, the field data will
+     * be ready to read. If it does not match, {@link #nextField()} or {@link #nextField(long)} will
+     * need to be called again before the field data can be read.
+     *
+     * @return true if fieldId matches the next field, false if not
+     */
+    public boolean nextField(long fieldId) throws IOException {
+        if (nextField() == (int) fieldId) {
+            return true;
+        }
+        // Note to reuse the info from the nextField call in the next call.
+        mState |= STATE_FIELD_MISS;
+        return false;
+    }
+
+    /**
+     * Read a single double.
+     * Will throw if the current wire type is not fixed64
+     *
+     * @param fieldId - must match the current field number and field type
+     */
+    public double readDouble(long fieldId) throws IOException {
+        assertFreshData();
+        assertFieldNumber(fieldId);
+        checkPacked(fieldId);
+
+        double value;
+        switch ((int) ((fieldId & FIELD_TYPE_MASK)
+                >>> FIELD_TYPE_SHIFT)) {
+            case (int) (FIELD_TYPE_DOUBLE >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_FIXED64);
+                value = Double.longBitsToDouble(readFixed64());
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Requested field id (" + getFieldIdString(fieldId)
+                                + ") cannot be read as a double"
+                                + dumpDebugData());
+        }
+        // Successfully read the field
+        mState &= ~STATE_STARTED_FIELD_READ;
+        return value;
+    }
+
+    /**
+     * Read a single float.
+     * Will throw if the current wire type is not fixed32
+     *
+     * @param fieldId - must match the current field number and field type
+     */
+    public float readFloat(long fieldId) throws IOException {
+        assertFreshData();
+        assertFieldNumber(fieldId);
+        checkPacked(fieldId);
+
+        float value;
+        switch ((int) ((fieldId & FIELD_TYPE_MASK)
+                >>> FIELD_TYPE_SHIFT)) {
+            case (int) (FIELD_TYPE_FLOAT >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_FIXED32);
+                value = Float.intBitsToFloat(readFixed32());
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Requested field id (" + getFieldIdString(fieldId) + ") is not a float"
+                                + dumpDebugData());
+        }
+        // Successfully read the field
+        mState &= ~STATE_STARTED_FIELD_READ;
+        return value;
+    }
+
+    /**
+     * Read a single 32bit or varint proto type field as an int.
+     * Will throw if the current wire type is not varint or fixed32
+     *
+     * @param fieldId - must match the current field number and field type
+     */
+    public int readInt(long fieldId) throws IOException {
+        assertFreshData();
+        assertFieldNumber(fieldId);
+        checkPacked(fieldId);
+
+        int value;
+        switch ((int) ((fieldId & FIELD_TYPE_MASK)
+                >>> FIELD_TYPE_SHIFT)) {
+            case (int) (FIELD_TYPE_FIXED32 >>> FIELD_TYPE_SHIFT):
+            case (int) (FIELD_TYPE_SFIXED32 >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_FIXED32);
+                value = readFixed32();
+                break;
+            case (int) (FIELD_TYPE_SINT32 >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_VARINT);
+                value = decodeZigZag32((int) readVarint());
+                break;
+            case (int) (FIELD_TYPE_INT32 >>> FIELD_TYPE_SHIFT):
+            case (int) (FIELD_TYPE_UINT32 >>> FIELD_TYPE_SHIFT):
+            case (int) (FIELD_TYPE_ENUM >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_VARINT);
+                value = (int) readVarint();
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Requested field id (" + getFieldIdString(fieldId) + ") is not an int"
+                                + dumpDebugData());
+        }
+        // Successfully read the field
+        mState &= ~STATE_STARTED_FIELD_READ;
+        return value;
+    }
+
+    /**
+     * Read a single 64bit or varint proto type field as an long.
+     *
+     * @param fieldId - must match the current field number
+     */
+    public long readLong(long fieldId) throws IOException {
+        assertFreshData();
+        assertFieldNumber(fieldId);
+        checkPacked(fieldId);
+
+        long value;
+        switch ((int) ((fieldId & FIELD_TYPE_MASK)
+                >>> FIELD_TYPE_SHIFT)) {
+            case (int) (FIELD_TYPE_FIXED64 >>> FIELD_TYPE_SHIFT):
+            case (int) (FIELD_TYPE_SFIXED64 >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_FIXED64);
+                value = readFixed64();
+                break;
+            case (int) (FIELD_TYPE_SINT64 >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_VARINT);
+                value = decodeZigZag64(readVarint());
+                break;
+            case (int) (FIELD_TYPE_INT64 >>> FIELD_TYPE_SHIFT):
+            case (int) (FIELD_TYPE_UINT64 >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_VARINT);
+                value = readVarint();
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Requested field id (" + getFieldIdString(fieldId) + ") is not an long"
+                                + dumpDebugData());
+        }
+        // Successfully read the field
+        mState &= ~STATE_STARTED_FIELD_READ;
+        return value;
+    }
+
+    /**
+     * Read a single 32bit or varint proto type field as an boolean.
+     *
+     * @param fieldId - must match the current field number
+     */
+    public boolean readBoolean(long fieldId) throws IOException {
+        assertFreshData();
+        assertFieldNumber(fieldId);
+        checkPacked(fieldId);
+
+        boolean value;
+        switch ((int) ((fieldId & FIELD_TYPE_MASK)
+                >>> FIELD_TYPE_SHIFT)) {
+            case (int) (FIELD_TYPE_BOOL >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_VARINT);
+                value = readVarint() != 0;
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Requested field id (" + getFieldIdString(fieldId) + ") is not an boolean"
+                                + dumpDebugData());
+        }
+        // Successfully read the field
+        mState &= ~STATE_STARTED_FIELD_READ;
+        return value;
+    }
+
+    /**
+     * Read a string field
+     *
+     * @param fieldId - must match the current field number
+     */
+    public String readString(long fieldId) throws IOException {
+        assertFreshData();
+        assertFieldNumber(fieldId);
+
+        String value;
+        switch ((int) ((fieldId & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) {
+            case (int) (FIELD_TYPE_STRING >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_LENGTH_DELIMITED);
+                int len = (int) readVarint();
+                value = readRawString(len);
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Requested field id(" + getFieldIdString(fieldId)
+                                + ") is not an string"
+                                + dumpDebugData());
+        }
+        // Successfully read the field
+        mState &= ~STATE_STARTED_FIELD_READ;
+        return value;
+    }
+
+    /**
+     * Read a bytes field
+     *
+     * @param fieldId - must match the current field number
+     */
+    public byte[] readBytes(long fieldId) throws IOException {
+        assertFreshData();
+        assertFieldNumber(fieldId);
+
+        byte[] value;
+        switch ((int) ((fieldId & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) {
+            case (int) (FIELD_TYPE_MESSAGE >>> FIELD_TYPE_SHIFT):
+            case (int) (FIELD_TYPE_BYTES >>> FIELD_TYPE_SHIFT):
+                assertWireType(WIRE_TYPE_LENGTH_DELIMITED);
+                int len = (int) readVarint();
+                value = readRawBytes(len);
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Requested field type (" + getFieldIdString(fieldId)
+                                + ") cannot be read as raw bytes"
+                                + dumpDebugData());
+        }
+        // Successfully read the field
+        mState &= ~STATE_STARTED_FIELD_READ;
+        return value;
+    }
+
+    /**
+     * Start the read of an embedded Object
+     *
+     * @param fieldId - must match the current field number
+     * @return a token. The token must be handed back when finished reading embedded Object
+     */
+    public long start(long fieldId) throws IOException {
+        assertFreshData();
+        assertFieldNumber(fieldId);
+        assertWireType(WIRE_TYPE_LENGTH_DELIMITED);
+
+        int messageSize = (int) readVarint();
+
+        if (mExpectedObjectTokenStack == null) {
+            mExpectedObjectTokenStack = new ArrayList<>();
+        }
+        if (++mDepth == mExpectedObjectTokenStack.size()) {
+            // Create a token to keep track of nested Object and extend the object stack
+            mExpectedObjectTokenStack.add(makeToken(0,
+                    (fieldId & FIELD_COUNT_REPEATED) == FIELD_COUNT_REPEATED, mDepth,
+                    (int) fieldId, getOffset() + messageSize));
+
+        } else {
+            // Create a token to keep track of nested Object
+            mExpectedObjectTokenStack.set(mDepth, makeToken(0,
+                    (fieldId & FIELD_COUNT_REPEATED) == FIELD_COUNT_REPEATED, mDepth,
+                    (int) fieldId, getOffset() + messageSize));
+        }
+
+        // Sanity check
+        if (mDepth > 0
+                && getOffsetFromToken(mExpectedObjectTokenStack.get(mDepth))
+                > getOffsetFromToken(mExpectedObjectTokenStack.get(mDepth - 1))) {
+            throw new ProtoParseException("Embedded Object ("
+                    + token2String(mExpectedObjectTokenStack.get(mDepth))
+                    + ") ends after of parent Objects's ("
+                    + token2String(mExpectedObjectTokenStack.get(mDepth - 1))
+                    + ") end"
+                    + dumpDebugData());
+        }
+        mState &= ~STATE_STARTED_FIELD_READ;
+        return mExpectedObjectTokenStack.get(mDepth);
+    }
+
+    /**
+     * Note the end of a nested object. Must be called to continue streaming the rest of the proto.
+     * end can be called mid object parse. The offset will be moved to the next field outside the
+     * object.
+     *
+     * @param token - token
+     */
+    public void end(long token) {
+        // Sanity check to make sure user is keeping track of their embedded messages
+        if (mExpectedObjectTokenStack.get(mDepth) != token) {
+            throw new ProtoParseException(
+                    "end token " + token + " does not match current message token "
+                            + mExpectedObjectTokenStack.get(mDepth)
+                            + dumpDebugData());
+        }
+        if (getOffsetFromToken(mExpectedObjectTokenStack.get(mDepth)) > getOffset()) {
+            // Did not read all of the message, skip to the end
+            incOffset(getOffsetFromToken(mExpectedObjectTokenStack.get(mDepth)) - getOffset());
+        }
+        mDepth--;
+        mState &= ~STATE_STARTED_FIELD_READ;
+    }
+
+    /**
+     * Read the tag at the start of the next field and collect field number and wire type.
+     * Will set mFieldNumber to NO_MORE_FIELDS if end of buffer/stream reached.
+     */
+    private void readTag() throws IOException {
+        fillBuffer();
+        if (mOffset >= mEnd) {
+            // reached end of the stream
+            mFieldNumber = NO_MORE_FIELDS;
+            return;
+        }
+        int tag = (int) readVarint();
+        mFieldNumber = tag >>> FIELD_ID_SHIFT;
+        mWireType = tag & WIRE_TYPE_MASK;
+        mState |= STATE_STARTED_FIELD_READ;
+    }
+
+    /**
+     * Decode a 32 bit ZigZag encoded signed int.
+     *
+     * @param n - int to decode
+     * @return the decoded signed int
+     */
+    public int decodeZigZag32(final int n) {
+        return (n >>> 1) ^ -(n & 1);
+    }
+
+    /**
+     * Decode a 64 bit ZigZag encoded signed long.
+     *
+     * @param n - long to decode
+     * @return the decoded signed long
+     */
+    public long decodeZigZag64(final long n) {
+        return (n >>> 1) ^ -(n & 1);
+    }
+
+    /**
+     * Read a varint from the buffer
+     *
+     * @return the varint as a long
+     */
+    private long readVarint() throws IOException {
+        long value = 0;
+        int shift = 0;
+        while (true) {
+            fillBuffer();
+            // Limit how much bookkeeping is done by checking how far away the end of the buffer is
+            // and directly accessing buffer up until the end.
+            final int fragment = mEnd - mOffset;
+            if (fragment < 0) {
+                throw new ProtoParseException(
+                        "Incomplete varint at offset 0x"
+                                + Integer.toHexString(getOffset())
+                                + dumpDebugData());
+            }
+            for (int i = 0; i < fragment; i++) {
+                byte b = mBuffer[(mOffset + i)];
+                value |= (b & 0x7FL) << shift;
+                if ((b & 0x80) == 0) {
+                    incOffset(i + 1);
+                    return value;
+                }
+                shift += 7;
+                if (shift > 63) {
+                    throw new ProtoParseException(
+                            "Varint is too large at offset 0x"
+                                    + Integer.toHexString(getOffset() + i)
+                                    + dumpDebugData());
+                }
+            }
+            // Hit the end of the buffer, do some incrementing and checking, then continue
+            incOffset(fragment);
+        }
+    }
+
+    /**
+     * Read a fixed 32 bit int from the buffer
+     *
+     * @return the fixed32 as a int
+     */
+    private int readFixed32() throws IOException {
+        // check for fast path, which is likely with a reasonable buffer size
+        if (mOffset + 4 <= mEnd) {
+            // don't bother filling buffer since we know the end is plenty far away
+            incOffset(4);
+            return (mBuffer[mOffset - 4] & 0xFF)
+                    | ((mBuffer[mOffset - 3] & 0xFF) << 8)
+                    | ((mBuffer[mOffset - 2] & 0xFF) << 16)
+                    | ((mBuffer[mOffset - 1] & 0xFF) << 24);
+        }
+
+        // the Fixed32 crosses the edge of a chunk, read the Fixed32 in multiple fragments.
+        // There will be two fragment reads except when the chunk size is 2 or less.
+        int value = 0;
+        int shift = 0;
+        int bytesLeft = 4;
+        while (bytesLeft > 0) {
+            fillBuffer();
+            // Find the number of bytes available until the end of the chunk or Fixed32
+            int fragment = (mEnd - mOffset) < bytesLeft ? (mEnd - mOffset) : bytesLeft;
+            if (fragment < 0) {
+                throw new ProtoParseException(
+                        "Incomplete fixed32 at offset 0x"
+                                + Integer.toHexString(getOffset())
+                                + dumpDebugData());
+            }
+            incOffset(fragment);
+            bytesLeft -= fragment;
+            while (fragment > 0) {
+                value |= ((mBuffer[mOffset - fragment] & 0xFF) << shift);
+                fragment--;
+                shift += 8;
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Read a fixed 64 bit long from the buffer
+     *
+     * @return the fixed64 as a long
+     */
+    private long readFixed64() throws IOException {
+        // check for fast path, which is likely with a reasonable buffer size
+        if (mOffset + 8 <= mEnd) {
+            // don't bother filling buffer since we know the end is plenty far away
+            incOffset(8);
+            return (mBuffer[mOffset - 8] & 0xFFL)
+                    | ((mBuffer[mOffset - 7] & 0xFFL) << 8)
+                    | ((mBuffer[mOffset - 6] & 0xFFL) << 16)
+                    | ((mBuffer[mOffset - 5] & 0xFFL) << 24)
+                    | ((mBuffer[mOffset - 4] & 0xFFL) << 32)
+                    | ((mBuffer[mOffset - 3] & 0xFFL) << 40)
+                    | ((mBuffer[mOffset - 2] & 0xFFL) << 48)
+                    | ((mBuffer[mOffset - 1] & 0xFFL) << 56);
+        }
+
+        // the Fixed64 crosses the edge of a chunk, read the Fixed64 in multiple fragments.
+        // There will be two fragment reads except when the chunk size is 6 or less.
+        long value = 0;
+        int shift = 0;
+        int bytesLeft = 8;
+        while (bytesLeft > 0) {
+            fillBuffer();
+            // Find the number of bytes available until the end of the chunk or Fixed64
+            int fragment = (mEnd - mOffset) < bytesLeft ? (mEnd - mOffset) : bytesLeft;
+            if (fragment < 0) {
+                throw new ProtoParseException(
+                        "Incomplete fixed64 at offset 0x"
+                                + Integer.toHexString(getOffset())
+                                + dumpDebugData());
+            }
+            incOffset(fragment);
+            bytesLeft -= fragment;
+            while (fragment > 0) {
+                value |= ((mBuffer[(mOffset - fragment)] & 0xFFL) << shift);
+                fragment--;
+                shift += 8;
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Read raw bytes from the buffer
+     *
+     * @param n - number of bytes to read
+     * @return a byte array with raw bytes
+     */
+    private byte[] readRawBytes(int n) throws IOException {
+        byte[] buffer = new byte[n];
+        int pos = 0;
+        while (mOffset + n - pos > mEnd) {
+            int fragment = mEnd - mOffset;
+            if (fragment > 0) {
+                System.arraycopy(mBuffer, mOffset, buffer, pos, fragment);
+                incOffset(fragment);
+                pos += fragment;
+            }
+            fillBuffer();
+            if (mOffset >= mEnd) {
+                throw new ProtoParseException(
+                        "Unexpectedly reached end of the InputStream at offset 0x"
+                                + Integer.toHexString(mEnd)
+                                + dumpDebugData());
+            }
+        }
+        System.arraycopy(mBuffer, mOffset, buffer, pos, n - pos);
+        incOffset(n - pos);
+        return buffer;
+    }
+
+    /**
+     * Read raw string from the buffer
+     *
+     * @param n - number of bytes to read
+     * @return a string
+     */
+    private String readRawString(int n) throws IOException {
+        fillBuffer();
+        if (mOffset + n <= mEnd) {
+            // fast path read. String is well within the current buffer
+            String value = new String(mBuffer, mOffset, n, StandardCharsets.UTF_8);
+            incOffset(n);
+            return value;
+        } else if (n <= mBufferSize) {
+            // String extends past buffer, but can be encapsulated in a buffer. Copy the first chunk
+            // of the string to the start of the buffer and then fill the rest of the buffer from
+            // the stream.
+            final int stringHead = mEnd - mOffset;
+            System.arraycopy(mBuffer, mOffset, mBuffer, 0, stringHead);
+            mEnd = stringHead + mStream.read(mBuffer, stringHead, n - stringHead);
+
+            mDiscardedBytes += mOffset;
+            mOffset = 0;
+
+            String value = new String(mBuffer, mOffset, n, StandardCharsets.UTF_8);
+            incOffset(n);
+            return value;
+        }
+        // Otherwise, the string is too large to use the buffer. Create the string from a
+        // separate byte array.
+        return new String(readRawBytes(n), 0, n, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Fill the buffer with a chunk from the stream if need be.
+     * Will skip chunks until mOffset is reached
+     */
+    private void fillBuffer() throws IOException {
+        if (mOffset >= mEnd && mStream != null) {
+            mOffset -= mEnd;
+            mDiscardedBytes += mEnd;
+            if (mOffset >= mBufferSize) {
+                int skipped = (int) mStream.skip((mOffset / mBufferSize) * mBufferSize);
+                mDiscardedBytes += skipped;
+                mOffset -= skipped;
+            }
+            mEnd = mStream.read(mBuffer);
+        }
+    }
+
+    /**
+     * Skips the rest of current field and moves to the start of the next field. This should only be
+     * called while state is STATE_STARTED_FIELD_READ
+     */
+    public void skip() throws IOException {
+        if ((mState & STATE_READING_PACKED) == STATE_READING_PACKED) {
+            incOffset(mPackedEnd - getOffset());
+        } else {
+            switch (mWireType) {
+                case WIRE_TYPE_VARINT:
+                    byte b;
+                    do {
+                        fillBuffer();
+                        b = mBuffer[mOffset];
+                        incOffset(1);
+                    } while ((b & 0x80) != 0);
+                    break;
+                case WIRE_TYPE_FIXED64:
+                    incOffset(8);
+                    break;
+                case WIRE_TYPE_LENGTH_DELIMITED:
+                    fillBuffer();
+                    int length = (int) readVarint();
+                    incOffset(length);
+                    break;
+                /*
+            case WIRE_TYPE_START_GROUP:
+                // Not implemented
+                break;
+            case WIRE_TYPE_END_GROUP:
+                // Not implemented
+                break;
+                */
+                case WIRE_TYPE_FIXED32:
+                    incOffset(4);
+                    break;
+                default:
+                    throw new ProtoParseException(
+                            "Unexpected wire type: " + mWireType + " at offset 0x"
+                                    + Integer.toHexString(mOffset)
+                                    + dumpDebugData());
+            }
+        }
+        mState &= ~STATE_STARTED_FIELD_READ;
+    }
+
+    /**
+     * Increment the offset and handle all the relevant bookkeeping
+     * Refilling the buffer when its end is reached will be handled elsewhere (ideally just before
+     * a read, to avoid unnecessary reads from stream)
+     *
+     * @param n - number of bytes to increment
+     */
+    private void incOffset(int n) {
+        mOffset += n;
+
+        if (mDepth >= 0 && getOffset() > getOffsetFromToken(
+                mExpectedObjectTokenStack.get(mDepth))) {
+            throw new ProtoParseException("Unexpectedly reached end of embedded object.  "
+                    + token2String(mExpectedObjectTokenStack.get(mDepth))
+                    + dumpDebugData());
+        }
+    }
+
+    /**
+     * Check the current wire type to determine if current numeric field is packed. If it is packed,
+     * set up to deal with the field
+     * This should only be called for primitive numeric field types.
+     *
+     * @param fieldId - used to determine what the packed wire type is.
+     */
+    private void checkPacked(long fieldId) throws IOException {
+        if (mWireType == WIRE_TYPE_LENGTH_DELIMITED) {
+            // Primitive Field is length delimited, must be a packed field.
+            final int length = (int) readVarint();
+            mPackedEnd = getOffset() + length;
+            mState |= STATE_READING_PACKED;
+
+            // Fake the wire type, based on the field type
+            switch ((int) ((fieldId & FIELD_TYPE_MASK)
+                    >>> FIELD_TYPE_SHIFT)) {
+                case (int) (FIELD_TYPE_FLOAT >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_FIXED32 >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_SFIXED32 >>> FIELD_TYPE_SHIFT):
+                    if (length % 4 != 0) {
+                        throw new IllegalArgumentException(
+                                "Requested field id (" + getFieldIdString(fieldId)
+                                        + ") packed length " + length
+                                        + " is not aligned for fixed32"
+                                        + dumpDebugData());
+                    }
+                    mWireType = WIRE_TYPE_FIXED32;
+                    break;
+                case (int) (FIELD_TYPE_DOUBLE >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_FIXED64 >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_SFIXED64 >>> FIELD_TYPE_SHIFT):
+                    if (length % 8 != 0) {
+                        throw new IllegalArgumentException(
+                                "Requested field id (" + getFieldIdString(fieldId)
+                                        + ") packed length " + length
+                                        + " is not aligned for fixed64"
+                                        + dumpDebugData());
+                    }
+                    mWireType = WIRE_TYPE_FIXED64;
+                    break;
+                case (int) (FIELD_TYPE_SINT32 >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_INT32 >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_UINT32 >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_SINT64 >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_INT64 >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_UINT64 >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_ENUM >>> FIELD_TYPE_SHIFT):
+                case (int) (FIELD_TYPE_BOOL >>> FIELD_TYPE_SHIFT):
+                    mWireType = WIRE_TYPE_VARINT;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Requested field id (" + getFieldIdString(fieldId)
+                                    + ") is not a packable field"
+                                    + dumpDebugData());
+            }
+        }
+    }
+
+
+    /**
+     * Check a field id constant against current field number
+     *
+     * @param fieldId - throws if fieldId does not match mFieldNumber
+     */
+    private void assertFieldNumber(long fieldId) {
+        if ((int) fieldId != mFieldNumber) {
+            throw new IllegalArgumentException("Requested field id (" + getFieldIdString(fieldId)
+                    + ") does not match current field number (0x" + Integer.toHexString(
+                    mFieldNumber)
+                    + ") at offset 0x" + Integer.toHexString(getOffset())
+                    + dumpDebugData());
+        }
+    }
+
+
+    /**
+     * Check a wire type against current wire type.
+     *
+     * @param wireType - throws if wireType does not match mWireType.
+     */
+    private void assertWireType(int wireType) {
+        if (wireType != mWireType) {
+            throw new WireTypeMismatchException(
+                    "Current wire type " + getWireTypeString(mWireType)
+                            + " does not match expected wire type " + getWireTypeString(wireType)
+                            + " at offset 0x" + Integer.toHexString(getOffset())
+                            + dumpDebugData());
+        }
+    }
+
+    /**
+     * Check if there is data ready to be read.
+     */
+    private void assertFreshData() {
+        if ((mState & STATE_STARTED_FIELD_READ) != STATE_STARTED_FIELD_READ) {
+            throw new ProtoParseException(
+                    "Attempting to read already read field at offset 0x" + Integer.toHexString(
+                            getOffset()) + dumpDebugData());
+        }
+    }
+
+    /**
+     * Dump debugging data about the buffer.
+     */
+    public String dumpDebugData() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("\nmFieldNumber : 0x" + Integer.toHexString(mFieldNumber));
+        sb.append("\nmWireType : 0x" + Integer.toHexString(mWireType));
+        sb.append("\nmState : 0x" + Integer.toHexString(mState));
+        sb.append("\nmDiscardedBytes : 0x" + Integer.toHexString(mDiscardedBytes));
+        sb.append("\nmOffset : 0x" + Integer.toHexString(mOffset));
+        sb.append("\nmExpectedObjectTokenStack : ");
+        if (mExpectedObjectTokenStack == null) {
+            sb.append("null");
+        } else {
+            sb.append(mExpectedObjectTokenStack);
+        }
+        sb.append("\nmDepth : 0x" + Integer.toHexString(mDepth));
+        sb.append("\nmBuffer : ");
+        if (mBuffer == null) {
+            sb.append("null");
+        } else {
+            sb.append(mBuffer);
+        }
+        sb.append("\nmBufferSize : 0x" + Integer.toHexString(mBufferSize));
+        sb.append("\nmEnd : 0x" + Integer.toHexString(mEnd));
+
+        return sb.toString();
+    }
+}
diff --git a/android/util/proto/ProtoOutputStream.java b/android/util/proto/ProtoOutputStream.java
new file mode 100644
index 0000000..5fcd38e
--- /dev/null
+++ b/android/util/proto/ProtoOutputStream.java
@@ -0,0 +1,2539 @@
+/*
+ * Copyright (C) 2012 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.proto;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Class to write to a protobuf stream.
+ *
+ * <p>
+ * This API is not as convenient or type safe as the standard protobuf
+ * classes. If possible, the best recommended library is to use protobuf lite.
+ * However, in environments (such as the Android platform itself), a
+ * more memory efficient version is necessary.
+ *
+ * <p>Each write method takes an ID code from the protoc generated classes
+ * and the value to write.  To make a nested object, call {@link #start(long)}
+ * and then {@link #end(long)} when you are done.
+ *
+ * <p>The ID codes have type information embedded into them, so if you call
+ * the incorrect function you will get an {@link IllegalArgumentException}.
+ *
+ * <p>To retrieve the encoded protobuf stream, call {@link #getBytes()}.
+ *
+ * stream as the top-level objects are finished.
+ *
+ */
+
+/* IMPLEMENTATION NOTES
+ *
+ * Because protobuf has inner values, and they are length prefixed, and
+ * those sizes themselves are stored with a variable length encoding, it
+ * is impossible to know how big an object will be in a single pass.
+ *
+ * The traditional way is to copy the in-memory representation of an object
+ * into the generated proto Message objects, do a traversal of those to
+ * cache the size, and then write the size-prefixed buffers.
+ *
+ * We are trying to avoid too much generated code here, but this class still
+ * needs to have a somewhat sane API.  We can't have the multiple passes be
+ * done by the calling code.  In addition, we want to avoid the memory high
+ * water mark of duplicating all of the values into the traditional in-memory
+ * Message objects. We need to find another way.
+ *
+ * So what we do here is to let the calling code write the data into a
+ * byte[] (actually a collection of them wrapped in the EncodedBuffer class),
+ * but not do the varint encoding of the sub-message sizes.  Then, we do a
+ * recursive traversal of the buffer itself, calculating the sizes (which are
+ * then knowable, although still not the actual sizes in the buffer because of
+ * possible further nesting).  Then we do a third pass, compacting the
+ * buffer and varint encoding the sizes.
+ *
+ * This gets us a relatively small number of fixed-size allocations,
+ * which is less likely to cause memory fragmentation or churn the GC, and
+ * the same number of data copies as we would have gotten with setting it
+ * field-by-field in generated code, and no code bloat from generated code.
+ * The final data copy is also done with System.arraycopy, which will be
+ * more efficient, in general, than doing the individual fields twice (as in
+ * the traditional way).
+ *
+ * To accomplish the multiple passes, whenever we write a
+ * WIRE_TYPE_LENGTH_DELIMITED field, we write the size occupied in our
+ * buffer as a fixed 32 bit int (called childRawSize), not a variable length
+ * one. We reserve another 32 bit slot for the computed size (called
+ * childEncodedSize).  If we know the size up front, as we do for strings
+ * and byte[], then we also put that into childEncodedSize, if we don't, we
+ * write the negative of childRawSize, as a sentinel that we need to
+ * compute it during the second pass and recursively compact it during the
+ * third pass.
+ *
+ * Unsigned size varints can be up to five bytes long, but we reserve eight
+ * bytes for overhead, so we know that when we compact the buffer, there
+ * will always be space for the encoded varint.
+ *
+ * When we can figure out the size ahead of time, we do, in order
+ * to save overhead with recalculating it, and with the later arraycopy.
+ *
+ * During the period between when the caller has called #start, but
+ * not yet called #end, we maintain a linked list of the tokens
+ * returned by #start, stored in those 8 bytes of size storage space.
+ * We use that linked list of tokens to ensure that the caller has
+ * correctly matched pairs of #start and #end calls, and issue
+ * errors if they are not matched.
+ */
+public final class ProtoOutputStream extends ProtoStream {
+    /**
+     * @hide
+     */
+    public static final String TAG = "ProtoOutputStream";
+
+    /**
+     * Our buffer.
+     */
+    private EncodedBuffer mBuffer;
+
+    /**
+     * Our stream.  If there is one.
+     */
+    private OutputStream mStream;
+
+    /**
+     * Current nesting depth of startObject calls.
+     */
+    private int mDepth;
+
+    /**
+     * An ID given to objects and returned in the token from startObject
+     * and stored in the buffer until endObject is called, where the two
+     * are checked.
+     *
+     * <p>Starts at -1 and becomes more negative, so the values
+     * aren't likely to alias with the size it will be overwritten with,
+     * which tend to be small, and we will be more likely to catch when
+     * the caller of endObject uses a stale token that they didn't intend
+     * to (e.g. copy and paste error).
+     */
+    private int mNextObjectId = -1;
+
+    /**
+     * The object token we are expecting in endObject.
+     *
+     * <p>If another call to startObject happens, this is written to that location, which gives
+     * us a stack, stored in the space for the as-yet unused size fields.
+     */
+    private long mExpectedObjectToken;
+
+    /**
+     * Index in mBuffer that we should start copying from on the next
+     * pass of compaction.
+     */
+    private int mCopyBegin;
+
+    /**
+     * Whether we've already compacted
+     */
+    private boolean mCompacted;
+
+    /**
+     * Construct a {@link ProtoOutputStream} with the default chunk size.
+     *
+     * <p>This is for an in-memory proto. The caller should use {@link #getBytes()} for the result.
+     */
+    public ProtoOutputStream() {
+        this(0);
+    }
+
+    /**
+     * Construct a {@link ProtoOutputStream with the given chunk size.
+     *
+     * <p>This is for an in-memory proto. The caller should use {@link #getBytes()} for the result.
+     */
+    public ProtoOutputStream(int chunkSize) {
+        mBuffer = new EncodedBuffer(chunkSize);
+    }
+
+    /**
+     * Construct a {@link ProtoOutputStream} that sits on top of an {@link OutputStream}.
+     *
+     * <p>The {@link #flush()} method must be called when done writing
+     * to flush any remaining data, although data *may* be written at intermediate
+     * points within the writing as well.
+     */
+    public ProtoOutputStream(@NonNull OutputStream stream) {
+        this();
+        mStream = stream;
+    }
+
+    /**
+     * Construct a {@link ProtoOutputStream} that sits on top of a {@link FileDescriptor}.
+     *
+     * <p>The {@link #flush()} method must be called when done writing
+     * to flush any remaining data, although data *may* be written at intermediate
+     * points within the writing as well.
+     *
+     * @hide
+     */
+    public ProtoOutputStream(@NonNull FileDescriptor fd) {
+        this(new FileOutputStream(fd));
+    }
+
+    /**
+     * Returns the total size of the data that has been written, after full
+     * protobuf encoding has occurred.
+     *
+     * @return the uncompressed buffer size
+     */
+    public int getRawSize() {
+        if (mCompacted) {
+            return getBytes().length;
+        } else {
+            return mBuffer.getSize();
+        }
+    }
+
+    /**
+     * Write a value for the given fieldId.
+     *
+     * <p>Will automatically convert for the following field types, and
+     * throw an exception for others: double, float, int32, int64, uint32, uint64,
+     * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, double val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // double
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeDoubleImpl(id, (double)val);
+                break;
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedDoubleImpl(id, (double)val);
+                break;
+            // float
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFloatImpl(id, (float)val);
+                break;
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFloatImpl(id, (float)val);
+                break;
+            // int32
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt32Impl(id, (int)val);
+                break;
+            // int64
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt64Impl(id, (long)val);
+                break;
+            // uint32
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt32Impl(id, (int)val);
+                break;
+            // uint64
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt64Impl(id, (long)val);
+                break;
+            // sint32
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt32Impl(id, (int)val);
+                break;
+            // sint64
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt64Impl(id, (long)val);
+                break;
+            // fixed32
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed32Impl(id, (int)val);
+                break;
+            // fixed64
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed64Impl(id, (long)val);
+                break;
+            // sfixed32
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed32Impl(id, (int)val);
+                break;
+            // sfixed64
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed64Impl(id, (long)val);
+                break;
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val != 0);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val != 0);
+                break;
+            // enum
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeEnumImpl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedEnumImpl(id, (int)val);
+                break;
+            // string, bytes, object not allowed here.
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, double) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a value for the given fieldId.
+     *
+     * <p>Will automatically convert for the following field types, and
+     * throw an exception for others: double, float, int32, int64, uint32, uint64,
+     * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, float val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // double
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeDoubleImpl(id, (double)val);
+                break;
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedDoubleImpl(id, (double)val);
+                break;
+            // float
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFloatImpl(id, (float)val);
+                break;
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFloatImpl(id, (float)val);
+                break;
+            // int32
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt32Impl(id, (int)val);
+                break;
+            // int64
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt64Impl(id, (long)val);
+                break;
+            // uint32
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt32Impl(id, (int)val);
+                break;
+            // uint64
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt64Impl(id, (long)val);
+                break;
+            // sint32
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt32Impl(id, (int)val);
+                break;
+            // sint64
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt64Impl(id, (long)val);
+                break;
+            // fixed32
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed32Impl(id, (int)val);
+                break;
+            // fixed64
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed64Impl(id, (long)val);
+                break;
+            // sfixed32
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed32Impl(id, (int)val);
+                break;
+            // sfixed64
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed64Impl(id, (long)val);
+                break;
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val != 0);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val != 0);
+                break;
+            // enum
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeEnumImpl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedEnumImpl(id, (int)val);
+                break;
+            // string, bytes, object not allowed here.
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, float) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a value for the given fieldId.
+     *
+     * <p>Will automatically convert for the following field types, and
+     * throw an exception for others: double, float, int32, int64, uint32, uint64,
+     * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // double
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeDoubleImpl(id, (double)val);
+                break;
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedDoubleImpl(id, (double)val);
+                break;
+            // float
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFloatImpl(id, (float)val);
+                break;
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFloatImpl(id, (float)val);
+                break;
+            // int32
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt32Impl(id, (int)val);
+                break;
+            // int64
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt64Impl(id, (long)val);
+                break;
+            // uint32
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt32Impl(id, (int)val);
+                break;
+            // uint64
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt64Impl(id, (long)val);
+                break;
+            // sint32
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt32Impl(id, (int)val);
+                break;
+            // sint64
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt64Impl(id, (long)val);
+                break;
+            // fixed32
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed32Impl(id, (int)val);
+                break;
+            // fixed64
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed64Impl(id, (long)val);
+                break;
+            // sfixed32
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed32Impl(id, (int)val);
+                break;
+            // sfixed64
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed64Impl(id, (long)val);
+                break;
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val != 0);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val != 0);
+                break;
+            // enum
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeEnumImpl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedEnumImpl(id, (int)val);
+                break;
+            // string, bytes, object not allowed here.
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, int) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a value for the given fieldId.
+     *
+     * <p>Will automatically convert for the following field types, and
+     * throw an exception for others: double, float, int32, int64, uint32, uint64,
+     * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // double
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeDoubleImpl(id, (double)val);
+                break;
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedDoubleImpl(id, (double)val);
+                break;
+            // float
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFloatImpl(id, (float)val);
+                break;
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFloatImpl(id, (float)val);
+                break;
+            // int32
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt32Impl(id, (int)val);
+                break;
+            // int64
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt64Impl(id, (long)val);
+                break;
+            // uint32
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt32Impl(id, (int)val);
+                break;
+            // uint64
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt64Impl(id, (long)val);
+                break;
+            // sint32
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt32Impl(id, (int)val);
+                break;
+            // sint64
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt64Impl(id, (long)val);
+                break;
+            // fixed32
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed32Impl(id, (int)val);
+                break;
+            // fixed64
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed64Impl(id, (long)val);
+                break;
+            // sfixed32
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed32Impl(id, (int)val);
+                break;
+            // sfixed64
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed64Impl(id, (long)val);
+                break;
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val != 0);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val != 0);
+                break;
+            // enum
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeEnumImpl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedEnumImpl(id, (int)val);
+                break;
+            // string, bytes, object not allowed here.
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, long) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a boolean value for the given fieldId.
+     *
+     * <p>If the field is not a bool field, an {@link IllegalStateException} will be thrown.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, boolean val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val);
+                break;
+            // nothing else allowed
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, boolean) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a string value for the given fieldId.
+     *
+     * <p>If the field is not a string field, an exception will be thrown.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, @Nullable String val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // string
+            case (int)((FIELD_TYPE_STRING | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeStringImpl(id, val);
+                break;
+            case (int)((FIELD_TYPE_STRING | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_STRING | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedStringImpl(id, val);
+                break;
+            // nothing else allowed
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, String) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a byte[] value for the given fieldId.
+     *
+     * <p>If the field is not a bytes or object field, an exception will be thrown.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, @Nullable byte[] val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int) ((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // bytes
+            case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBytesImpl(id, val);
+                break;
+            case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBytesImpl(id, val);
+                break;
+            // Object
+            case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeObjectImpl(id, val);
+                break;
+            case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedObjectImpl(id, val);
+                break;
+            // nothing else allowed
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, byte[]) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Start a sub object.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @return The token to call {@link #end(long)} with.
+     */
+    public long start(long fieldId) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_MESSAGE) {
+            final long count = fieldId & FIELD_COUNT_MASK;
+            if (count == FIELD_COUNT_SINGLE) {
+                return startObjectImpl(id, false);
+            } else if (count == FIELD_COUNT_REPEATED || count == FIELD_COUNT_PACKED) {
+                return startObjectImpl(id, true);
+            }
+        }
+        throw new IllegalArgumentException("Attempt to call start(long) with "
+                + getFieldIdString(fieldId));
+    }
+
+    /**
+     * End the object started by start() that returned token.
+     *
+     * @param token The token returned from {@link #start(long)}
+     */
+    public void end(long token) {
+        endObjectImpl(token, getRepeatedFromToken(token));
+    }
+
+    //
+    // proto3 type: double
+    // java type: double
+    // encoding: fixed64
+    // wire type: WIRE_TYPE_FIXED64
+    //
+
+    /**
+     * Write a single proto "double" type field value.
+     *
+     * @deprecated Use {@link #write(long, double)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeDouble(long fieldId, double val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE);
+
+        writeDoubleImpl(id, val);
+    }
+
+    private void writeDoubleImpl(int id, double val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_FIXED64);
+            mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
+        }
+    }
+
+    /**
+     * Write a single repeated proto "double" type field value.
+     *
+     * @deprecated Use {@link #write(long, double)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedDouble(long fieldId, double val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_DOUBLE);
+
+        writeRepeatedDoubleImpl(id, val);
+    }
+
+    private void writeRepeatedDoubleImpl(int id, double val) {
+        writeTag(id, WIRE_TYPE_FIXED64);
+        mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
+    }
+
+    /**
+     * Write a list of packed proto "double" type field values.
+     *
+     * @deprecated Use {@link #write(long, double)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedDouble(long fieldId, @Nullable double[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_DOUBLE);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            writeKnownLengthHeader(id, N * 8);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawFixed64(Double.doubleToLongBits(val[i]));
+            }
+        }
+    }
+
+    //
+    // proto3 type: float
+    // java type: float
+    // encoding: fixed32
+    // wire type: WIRE_TYPE_FIXED32
+    //
+
+    /**
+     * Write a single proto "float" type field value.
+     *
+     * @deprecated Use {@link #write(long, float)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeFloat(long fieldId, float val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT);
+
+        writeFloatImpl(id, val);
+    }
+
+    private void writeFloatImpl(int id, float val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_FIXED32);
+            mBuffer.writeRawFixed32(Float.floatToIntBits(val));
+        }
+    }
+
+    /**
+     * Write a single repeated proto "float" type field value.
+     *
+     * @deprecated Use {@link #write(long, float)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedFloat(long fieldId, float val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FLOAT);
+
+        writeRepeatedFloatImpl(id, val);
+    }
+
+    private void writeRepeatedFloatImpl(int id, float val) {
+        writeTag(id, WIRE_TYPE_FIXED32);
+        mBuffer.writeRawFixed32(Float.floatToIntBits(val));
+    }
+
+    /**
+     * Write a list of packed proto "float" type field value.
+     *
+     * @deprecated Use {@link #write(long, float)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedFloat(long fieldId, @Nullable float[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FLOAT);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            writeKnownLengthHeader(id, N * 4);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawFixed32(Float.floatToIntBits(val[i]));
+            }
+        }
+    }
+
+    //
+    // proto3 type: int32
+    // java type: int
+    // signed/unsigned: signed
+    // encoding: varint
+    // wire type: WIRE_TYPE_VARINT
+    //
+
+    /**
+     * Writes a java int as an usigned varint.
+     *
+     * <p>The unadorned int32 type in protobuf is unfortunate because it
+     * is stored in memory as a signed value, but encodes as unsigned
+     * varints, which are formally always longs.  So here, we encode
+     * negative values as 64 bits, which will get the sign-extension,
+     * and positive values as 32 bits, which saves a marginal amount
+     * of work in that it processes ints instead of longs.
+     */
+    private void writeUnsignedVarintFromSignedInt(int val) {
+        if (val >= 0) {
+            mBuffer.writeRawVarint32(val);
+        } else {
+            mBuffer.writeRawVarint64(val);
+        }
+    }
+
+    /**
+     * Write a single proto "int32" type field value.
+     *
+     * <p>Note that these are stored in memory as signed values and written as unsigned
+     * varints, which if negative, are 10 bytes long. If you know the data is likely
+     * to be negative, use "sint32".
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeInt32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT32);
+
+        writeInt32Impl(id, val);
+    }
+
+    private void writeInt32Impl(int id, int val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_VARINT);
+            writeUnsignedVarintFromSignedInt(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "int32" type field value.
+     *
+     * <p>Note that these are stored in memory as signed values and written as unsigned
+     * varints, which if negative, are 10 bytes long. If you know the data is likely
+     * to be negative, use "sint32".
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedInt32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT32);
+
+        writeRepeatedInt32Impl(id, val);
+    }
+
+    private void writeRepeatedInt32Impl(int id, int val) {
+        writeTag(id, WIRE_TYPE_VARINT);
+        writeUnsignedVarintFromSignedInt(val);
+    }
+
+    /**
+     * Write a list of packed proto "int32" type field value.
+     *
+     * <p>Note that these are stored in memory as signed values and written as unsigned
+     * varints, which if negative, are 10 bytes long. If you know the data is likely
+     * to be negative, use "sint32".
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedInt32(long fieldId, @Nullable int[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_INT32);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            int size = 0;
+            for (int i=0; i<N; i++) {
+                final int v = val[i];
+                size += v >= 0 ? EncodedBuffer.getRawVarint32Size(v) : 10;
+            }
+            writeKnownLengthHeader(id, size);
+            for (int i=0; i<N; i++) {
+                writeUnsignedVarintFromSignedInt(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: int64
+    // java type: int
+    // signed/unsigned: signed
+    // encoding: varint
+    // wire type: WIRE_TYPE_VARINT
+    //
+
+    /**
+     * Write a single proto "int64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeInt64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT64);
+
+        writeInt64Impl(id, val);
+    }
+
+    private void writeInt64Impl(int id, long val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_VARINT);
+            mBuffer.writeRawVarint64(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "int64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedInt64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT64);
+
+        writeRepeatedInt64Impl(id, val);
+    }
+
+    private void writeRepeatedInt64Impl(int id, long val) {
+        writeTag(id, WIRE_TYPE_VARINT);
+        mBuffer.writeRawVarint64(val);
+    }
+
+    /**
+     * Write a list of packed proto "int64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedInt64(long fieldId, @Nullable long[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_INT64);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            int size = 0;
+            for (int i=0; i<N; i++) {
+                size += EncodedBuffer.getRawVarint64Size(val[i]);
+            }
+            writeKnownLengthHeader(id, size);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawVarint64(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: uint32
+    // java type: int
+    // signed/unsigned: unsigned
+    // encoding: varint
+    // wire type: WIRE_TYPE_VARINT
+    //
+
+    /**
+     * Write a single proto "uint32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeUInt32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32);
+
+        writeUInt32Impl(id, val);
+    }
+
+    private void writeUInt32Impl(int id, int val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_VARINT);
+            mBuffer.writeRawVarint32(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "uint32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedUInt32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT32);
+
+        writeRepeatedUInt32Impl(id, val);
+    }
+
+    private void writeRepeatedUInt32Impl(int id, int val) {
+        writeTag(id, WIRE_TYPE_VARINT);
+        mBuffer.writeRawVarint32(val);
+    }
+
+    /**
+     * Write a list of packed proto "uint32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedUInt32(long fieldId, @Nullable int[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_UINT32);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            int size = 0;
+            for (int i=0; i<N; i++) {
+                size += EncodedBuffer.getRawVarint32Size(val[i]);
+            }
+            writeKnownLengthHeader(id, size);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawVarint32(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: uint64
+    // java type: int
+    // signed/unsigned: unsigned
+    // encoding: varint
+    // wire type: WIRE_TYPE_VARINT
+    //
+
+    /**
+     * Write a single proto "uint64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeUInt64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64);
+
+        writeUInt64Impl(id, val);
+    }
+
+    private void writeUInt64Impl(int id, long val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_VARINT);
+            mBuffer.writeRawVarint64(val);
+        }
+    }
+
+    /**
+     * Write a single proto "uint64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedUInt64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT64);
+
+        writeRepeatedUInt64Impl(id, val);
+    }
+
+    private void writeRepeatedUInt64Impl(int id, long val) {
+        writeTag(id, WIRE_TYPE_VARINT);
+        mBuffer.writeRawVarint64(val);
+    }
+
+    /**
+     * Write a single proto "uint64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedUInt64(long fieldId, @Nullable long[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_UINT64);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            int size = 0;
+            for (int i=0; i<N; i++) {
+                size += EncodedBuffer.getRawVarint64Size(val[i]);
+            }
+            writeKnownLengthHeader(id, size);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawVarint64(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: sint32
+    // java type: int
+    // signed/unsigned: signed
+    // encoding: zig-zag
+    // wire type: WIRE_TYPE_VARINT
+    //
+
+    /**
+     * Write a single proto "sint32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeSInt32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32);
+
+        writeSInt32Impl(id, val);
+    }
+
+    private void writeSInt32Impl(int id, int val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_VARINT);
+            mBuffer.writeRawZigZag32(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "sint32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedSInt32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT32);
+
+        writeRepeatedSInt32Impl(id, val);
+    }
+
+    private void writeRepeatedSInt32Impl(int id, int val) {
+        writeTag(id, WIRE_TYPE_VARINT);
+        mBuffer.writeRawZigZag32(val);
+    }
+
+    /**
+     * Write a list of packed proto "sint32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedSInt32(long fieldId, @Nullable int[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SINT32);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            int size = 0;
+            for (int i=0; i<N; i++) {
+                size += EncodedBuffer.getRawZigZag32Size(val[i]);
+            }
+            writeKnownLengthHeader(id, size);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawZigZag32(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: sint64
+    // java type: int
+    // signed/unsigned: signed
+    // encoding: zig-zag
+    // wire type: WIRE_TYPE_VARINT
+    //
+
+    /**
+     * Write a single proto "sint64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeSInt64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64);
+
+        writeSInt64Impl(id, val);
+    }
+
+    private void writeSInt64Impl(int id, long val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_VARINT);
+            mBuffer.writeRawZigZag64(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "sint64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedSInt64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT64);
+
+        writeRepeatedSInt64Impl(id, val);
+    }
+
+    private void writeRepeatedSInt64Impl(int id, long val) {
+        writeTag(id, WIRE_TYPE_VARINT);
+        mBuffer.writeRawZigZag64(val);
+    }
+
+    /**
+     * Write a list of packed proto "sint64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedSInt64(long fieldId, @Nullable long[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SINT64);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            int size = 0;
+            for (int i=0; i<N; i++) {
+                size += EncodedBuffer.getRawZigZag64Size(val[i]);
+            }
+            writeKnownLengthHeader(id, size);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawZigZag64(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: fixed32
+    // java type: int
+    // encoding: little endian
+    // wire type: WIRE_TYPE_FIXED32
+    //
+
+    /**
+     * Write a single proto "fixed32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeFixed32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32);
+
+        writeFixed32Impl(id, val);
+    }
+
+    private void writeFixed32Impl(int id, int val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_FIXED32);
+            mBuffer.writeRawFixed32(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "fixed32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedFixed32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED32);
+
+        writeRepeatedFixed32Impl(id, val);
+    }
+
+    private void writeRepeatedFixed32Impl(int id, int val) {
+        writeTag(id, WIRE_TYPE_FIXED32);
+        mBuffer.writeRawFixed32(val);
+    }
+
+    /**
+     * Write a list of packed proto "fixed32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedFixed32(long fieldId, @Nullable int[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FIXED32);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            writeKnownLengthHeader(id, N * 4);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawFixed32(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: fixed64
+    // java type: long
+    // encoding: fixed64
+    // wire type: WIRE_TYPE_FIXED64
+    //
+
+    /**
+     * Write a single proto "fixed64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeFixed64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64);
+
+        writeFixed64Impl(id, val);
+    }
+
+    private void writeFixed64Impl(int id, long val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_FIXED64);
+            mBuffer.writeRawFixed64(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "fixed64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedFixed64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED64);
+
+        writeRepeatedFixed64Impl(id, val);
+    }
+
+    private void writeRepeatedFixed64Impl(int id, long val) {
+        writeTag(id, WIRE_TYPE_FIXED64);
+        mBuffer.writeRawFixed64(val);
+    }
+
+    /**
+     * Write a list of packed proto "fixed64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedFixed64(long fieldId, @Nullable long[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FIXED64);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            writeKnownLengthHeader(id, N * 8);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawFixed64(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: sfixed32
+    // java type: int
+    // encoding: little endian
+    // wire type: WIRE_TYPE_FIXED32
+    //
+    /**
+     * Write a single proto "sfixed32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeSFixed32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32);
+
+        writeSFixed32Impl(id, val);
+    }
+
+    private void writeSFixed32Impl(int id, int val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_FIXED32);
+            mBuffer.writeRawFixed32(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "sfixed32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedSFixed32(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED32);
+
+        writeRepeatedSFixed32Impl(id, val);
+    }
+
+    private void writeRepeatedSFixed32Impl(int id, int val) {
+        writeTag(id, WIRE_TYPE_FIXED32);
+        mBuffer.writeRawFixed32(val);
+    }
+
+    /**
+     * Write a list of packed proto "sfixed32" type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedSFixed32(long fieldId, @Nullable int[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SFIXED32);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            writeKnownLengthHeader(id, N * 4);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawFixed32(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: sfixed64
+    // java type: long
+    // encoding: little endian
+    // wire type: WIRE_TYPE_FIXED64
+    //
+
+    /**
+     * Write a single proto "sfixed64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeSFixed64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64);
+
+        writeSFixed64Impl(id, val);
+    }
+
+    private void writeSFixed64Impl(int id, long val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_FIXED64);
+            mBuffer.writeRawFixed64(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "sfixed64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedSFixed64(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED64);
+
+        writeRepeatedSFixed64Impl(id, val);
+    }
+
+    private void writeRepeatedSFixed64Impl(int id, long val) {
+        writeTag(id, WIRE_TYPE_FIXED64);
+        mBuffer.writeRawFixed64(val);
+    }
+
+    /**
+     * Write a list of packed proto "sfixed64" type field value.
+     *
+     * @deprecated Use {@link #write(long, long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedSFixed64(long fieldId, @Nullable long[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SFIXED64);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            writeKnownLengthHeader(id, N * 8);
+            for (int i=0; i<N; i++) {
+                mBuffer.writeRawFixed64(val[i]);
+            }
+        }
+    }
+
+    //
+    // proto3 type: bool
+    // java type: boolean
+    // encoding: varint
+    // wire type: WIRE_TYPE_VARINT
+    //
+
+    /**
+     * Write a single proto "bool" type field value.
+     *
+     * @deprecated Use {@link #write(long, boolean)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeBool(long fieldId, boolean val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL);
+
+        writeBoolImpl(id, val);
+    }
+
+    private void writeBoolImpl(int id, boolean val) {
+        if (val) {
+            writeTag(id, WIRE_TYPE_VARINT);
+            // 0 and 1 are the same as their varint counterparts
+            mBuffer.writeRawByte((byte)1);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "bool" type field value.
+     *
+     * @deprecated Use {@link #write(long, boolean)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedBool(long fieldId, boolean val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BOOL);
+
+        writeRepeatedBoolImpl(id, val);
+    }
+
+    private void writeRepeatedBoolImpl(int id, boolean val) {
+        writeTag(id, WIRE_TYPE_VARINT);
+        mBuffer.writeRawByte((byte)(val ? 1 : 0));
+    }
+
+    /**
+     * Write a list of packed proto "bool" type field value.
+     *
+     * @deprecated Use {@link #write(long, boolean)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedBool(long fieldId, @Nullable boolean[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_BOOL);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            // Write the header
+            writeKnownLengthHeader(id, N);
+
+            // Write the data
+            for (int i=0; i<N; i++) {
+                // 0 and 1 are the same as their varint counterparts
+                mBuffer.writeRawByte((byte)(val[i] ? 1 : 0));
+            }
+        }
+    }
+
+    //
+    // proto3 type: string
+    // java type: String
+    // encoding: utf-8
+    // wire type: WIRE_TYPE_LENGTH_DELIMITED
+    //
+
+    /**
+     * Write a single proto "string" type field value.
+     *
+     * @deprecated Use {@link #write(long, String)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeString(long fieldId, @Nullable String val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_STRING);
+
+        writeStringImpl(id, val);
+    }
+
+    private void writeStringImpl(int id, String val) {
+        if (val != null && val.length() > 0) {
+            writeUtf8String(id, val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "string" type field value.
+     *
+     * @deprecated Use {@link #write(long, String)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedString(long fieldId, @Nullable String val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_STRING);
+
+        writeRepeatedStringImpl(id, val);
+    }
+
+    private void writeRepeatedStringImpl(int id, String val) {
+        if (val == null || val.length() == 0) {
+            writeKnownLengthHeader(id, 0);
+        } else {
+            writeUtf8String(id, val);
+        }
+    }
+
+    /**
+     * Write a list of packed proto "string" type field value.
+     */
+    private void writeUtf8String(int id, String val) {
+        // TODO: Is it worth converting by hand in order to not allocate?
+        try {
+            final byte[] buf = val.getBytes("UTF-8");
+            writeKnownLengthHeader(id, buf.length);
+            mBuffer.writeRawBuffer(buf);
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException("not possible");
+        }
+    }
+
+    //
+    // proto3 type: bytes
+    // java type: byte[]
+    // encoding: varint
+    // wire type: WIRE_TYPE_VARINT
+    //
+
+    /**
+     * Write a single proto "bytes" type field value.
+     *
+     * @deprecated Use {@link #write(long, byte[])} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeBytes(long fieldId, @Nullable byte[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES);
+
+        writeBytesImpl(id, val);
+    }
+
+    private void writeBytesImpl(int id, byte[] val) {
+        if (val != null && val.length > 0) {
+            writeKnownLengthHeader(id, val.length);
+            mBuffer.writeRawBuffer(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto "bytes" type field value.
+     *
+     * @deprecated Use {@link #write(long, byte[])} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedBytes(long fieldId, @Nullable byte[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BYTES);
+
+        writeRepeatedBytesImpl(id, val);
+    }
+
+    private void writeRepeatedBytesImpl(int id, byte[] val) {
+        writeKnownLengthHeader(id, val == null ? 0 : val.length);
+        mBuffer.writeRawBuffer(val);
+    }
+
+    //
+    // proto3 type: enum
+    // java type: int
+    // signed/unsigned: unsigned
+    // encoding: varint
+    // wire type: WIRE_TYPE_VARINT
+    //
+
+    /**
+     * Write a single proto enum type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeEnum(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM);
+
+        writeEnumImpl(id, val);
+    }
+
+    private void writeEnumImpl(int id, int val) {
+        if (val != 0) {
+            writeTag(id, WIRE_TYPE_VARINT);
+            writeUnsignedVarintFromSignedInt(val);
+        }
+    }
+
+    /**
+     * Write a single repeated proto enum type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedEnum(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_ENUM);
+
+        writeRepeatedEnumImpl(id, val);
+    }
+
+    private void writeRepeatedEnumImpl(int id, int val) {
+        writeTag(id, WIRE_TYPE_VARINT);
+        writeUnsignedVarintFromSignedInt(val);
+    }
+
+    /**
+     * Write a list of packed proto enum type field value.
+     *
+     * @deprecated Use {@link #write(long, int)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writePackedEnum(long fieldId, @Nullable int[] val) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_ENUM);
+
+        final int N = val != null ? val.length : 0;
+        if (N > 0) {
+            int size = 0;
+            for (int i=0; i<N; i++) {
+                final int v = val[i];
+                size += v >= 0 ? EncodedBuffer.getRawVarint32Size(v) : 10;
+            }
+            writeKnownLengthHeader(id, size);
+            for (int i=0; i<N; i++) {
+                writeUnsignedVarintFromSignedInt(val[i]);
+            }
+        }
+    }
+
+
+    /**
+     * Start a child object.
+     *
+     * Returns a token which should be passed to endObject.  Calls to endObject must be
+     * nested properly.
+     *
+     * @deprecated Use {@link #start(long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public long startObject(long fieldId) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
+
+        return startObjectImpl(id, false);
+    }
+
+    /**
+     * End a child object. Pass in the token from the correspoinding startObject call.
+     *
+     * @deprecated Use {@link #end(long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void endObject(long token) {
+        assertNotCompacted();
+
+        endObjectImpl(token, false);
+    }
+
+    /**
+     * Start a repeated child object.
+     *
+     * Returns a token which should be passed to endObject.  Calls to endObject must be
+     * nested properly.
+     *
+     * @deprecated Use {@link #start(long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public long startRepeatedObject(long fieldId) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
+
+        return startObjectImpl(id, true);
+    }
+
+    /**
+     * End a child object. Pass in the token from the correspoinding startRepeatedObject call.
+     *
+     * @deprecated Use {@link #end(long)} instead.
+     * @hide
+     */
+    @Deprecated
+    public void endRepeatedObject(long token) {
+        assertNotCompacted();
+
+        endObjectImpl(token, true);
+    }
+
+    /**
+     * Common implementation of startObject and startRepeatedObject.
+     */
+    private long startObjectImpl(final int id, boolean repeated) {
+        writeTag(id, WIRE_TYPE_LENGTH_DELIMITED);
+        final int sizePos = mBuffer.getWritePos();
+        mDepth++;
+        mNextObjectId--;
+
+        // Write the previous token, giving us a stack of expected tokens.
+        // After endObject returns, the first fixed32 becomeschildRawSize (set in endObject)
+        // and the second one becomes childEncodedSize (set in editEncodedSize).
+        mBuffer.writeRawFixed32((int)(mExpectedObjectToken >> 32));
+        mBuffer.writeRawFixed32((int)mExpectedObjectToken);
+
+        long old = mExpectedObjectToken;
+
+        mExpectedObjectToken = makeToken(getTagSize(id), repeated, mDepth, mNextObjectId, sizePos);
+        return mExpectedObjectToken;
+    }
+
+    /**
+     * Common implementation of endObject and endRepeatedObject.
+     */
+    private void endObjectImpl(long token, boolean repeated) {
+        // The upper 32 bits of the token is the depth of startObject /
+        // endObject calls.  We could get aritrarily sophisticated, but
+        // that's enough to prevent the common error of missing an
+        // endObject somewhere.
+        // The lower 32 bits of the token is the offset in the buffer
+        // at which to write the size.
+        final int depth = getDepthFromToken(token);
+        final boolean expectedRepeated = getRepeatedFromToken(token);
+        final int sizePos = getOffsetFromToken(token);
+        final int childRawSize = mBuffer.getWritePos() - sizePos - 8;
+
+        if (repeated != expectedRepeated) {
+            if (repeated) {
+                throw new IllegalArgumentException("endRepeatedObject called where endObject should"
+                        + " have been");
+            } else {
+                throw new IllegalArgumentException("endObject called where endRepeatedObject should"
+                        + " have been");
+            }
+        }
+
+        // Check that we're getting the token and depth that we are expecting.
+        if ((mDepth & 0x01ff) != depth || mExpectedObjectToken != token) {
+            // This text of exception is united tested.  That test also implicity checks
+            // that we're tracking the objectIds and depths correctly.
+            throw new IllegalArgumentException("Mismatched startObject/endObject calls."
+                    + " Current depth " + mDepth
+                    + " token=" + token2String(token)
+                    + " expectedToken=" + token2String(mExpectedObjectToken));
+        }
+
+        // Get the next expected token that we stashed away in the buffer.
+        mExpectedObjectToken = (((long)mBuffer.getRawFixed32At(sizePos)) << 32)
+                | (0x0ffffffffL & (long)mBuffer.getRawFixed32At(sizePos+4));
+
+        mDepth--;
+        if (childRawSize > 0) {
+            mBuffer.editRawFixed32(sizePos, -childRawSize);
+            mBuffer.editRawFixed32(sizePos+4, -1);
+        } else if (repeated) {
+            mBuffer.editRawFixed32(sizePos, 0);
+            mBuffer.editRawFixed32(sizePos+4, 0);
+        } else {
+            // The object has no data.  Don't include it.
+            mBuffer.rewindWriteTo(sizePos - getTagSizeFromToken(token));
+        }
+    }
+
+    /**
+     * Write an object that has already been flattened.
+     *
+     * @deprecated Use {@link #write(long, byte[])} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeObject(long fieldId, @Nullable byte[] value) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
+
+        writeObjectImpl(id, value);
+    }
+
+    void writeObjectImpl(int id, byte[] value) {
+        if (value != null && value.length != 0) {
+            writeKnownLengthHeader(id, value.length);
+            mBuffer.writeRawBuffer(value);
+        }
+    }
+
+    /**
+     * Write an object that has already been flattened.
+     *
+     * @deprecated Use {@link #write(long, byte[])} instead.
+     * @hide
+     */
+    @Deprecated
+    public void writeRepeatedObject(long fieldId, @Nullable byte[] value) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
+
+        writeRepeatedObjectImpl(id, value);
+    }
+
+    void writeRepeatedObjectImpl(int id, byte[] value) {
+        writeKnownLengthHeader(id, value == null ? 0 : value.length);
+        mBuffer.writeRawBuffer(value);
+    }
+
+    //
+    // Tags
+    //
+
+    /**
+     * Combine a fieldId (the field keys in the proto file) and the field flags.
+     * Mostly useful for testing because the generated code contains the fieldId
+     * constants.
+     */
+    public static long makeFieldId(int id, long fieldFlags) {
+        return fieldFlags | (((long)id) & 0x0ffffffffL);
+    }
+
+    /**
+     * Validates that the fieldId provided is of the type and count from expectedType.
+     *
+     * <p>The type must match exactly to pass this check.
+     *
+     * <p>The count must match according to this truth table to pass the check:
+     *
+     *                  expectedFlags
+     *                  UNKNOWN     SINGLE      REPEATED    PACKED
+     *    fieldId
+     *    UNKNOWN       true        false       false       false
+     *    SINGLE        x           true        false       false
+     *    REPEATED      x           false       true        false
+     *    PACKED        x           false       true        true
+     *
+     * @throws {@link IllegalArgumentException} if it is not.
+     *
+     * @return The raw ID of that field.
+     */
+    public static int checkFieldId(long fieldId, long expectedFlags) {
+        final long fieldCount = fieldId & FIELD_COUNT_MASK;
+        final long fieldType = fieldId & FIELD_TYPE_MASK;
+        final long expectedCount = expectedFlags & FIELD_COUNT_MASK;
+        final long expectedType = expectedFlags & FIELD_TYPE_MASK;
+        if (((int)fieldId) == 0) {
+            throw new IllegalArgumentException("Invalid proto field " + (int)fieldId
+                    + " fieldId=" + Long.toHexString(fieldId));
+        }
+        if (fieldType != expectedType
+                || !((fieldCount == expectedCount)
+                    || (fieldCount == FIELD_COUNT_PACKED
+                        && expectedCount == FIELD_COUNT_REPEATED))) {
+            final String countString = getFieldCountString(fieldCount);
+            final String typeString = getFieldTypeString(fieldType);
+            if (typeString != null && countString != null) {
+                final StringBuilder sb = new StringBuilder();
+                if (expectedType == FIELD_TYPE_MESSAGE) {
+                    sb.append("start");
+                } else {
+                    sb.append("write");
+                }
+                sb.append(getFieldCountString(expectedCount));
+                sb.append(getFieldTypeString(expectedType));
+                sb.append(" called for field ");
+                sb.append((int)fieldId);
+                sb.append(" which should be used with ");
+                if (fieldType == FIELD_TYPE_MESSAGE) {
+                    sb.append("start");
+                } else {
+                    sb.append("write");
+                }
+                sb.append(countString);
+                sb.append(typeString);
+                if (fieldCount == FIELD_COUNT_PACKED) {
+                    sb.append(" or writeRepeated");
+                    sb.append(typeString);
+                }
+                sb.append('.');
+                throw new IllegalArgumentException(sb.toString());
+            } else {
+                final StringBuilder sb = new StringBuilder();
+                if (expectedType == FIELD_TYPE_MESSAGE) {
+                    sb.append("start");
+                } else {
+                    sb.append("write");
+                }
+                sb.append(getFieldCountString(expectedCount));
+                sb.append(getFieldTypeString(expectedType));
+                sb.append(" called with an invalid fieldId: 0x");
+                sb.append(Long.toHexString(fieldId));
+                sb.append(". The proto field ID might be ");
+                sb.append((int)fieldId);
+                sb.append('.');
+                throw new IllegalArgumentException(sb.toString());
+            }
+        }
+        return (int)fieldId;
+    }
+
+    /**
+     * Return how many bytes an encoded field tag will require.
+     */
+    private static int getTagSize(int id) {
+        return EncodedBuffer.getRawVarint32Size(id << FIELD_ID_SHIFT);
+    }
+
+    /**
+     * Write an individual field tag by hand.
+     *
+     * See <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a> for details on the structure of how tags and data are written.
+     */
+    public void writeTag(int id, @WireType int wireType) {
+        mBuffer.writeRawVarint32((id << FIELD_ID_SHIFT) | wireType);
+    }
+
+    /**
+     * Write the header of a WIRE_TYPE_LENGTH_DELIMITED field for one where
+     * we know the size in advance and do not need to compute and compact.
+     */
+    private void writeKnownLengthHeader(int id, int size) {
+        // Write the tag
+        writeTag(id, WIRE_TYPE_LENGTH_DELIMITED);
+        // Size will be compacted later, but we know the size, so write it,
+        // once for the rawSize and once for the encodedSize.
+        mBuffer.writeRawFixed32(size);
+        mBuffer.writeRawFixed32(size);
+    }
+
+    //
+    // Getting the buffer and compaction
+    //
+
+    /**
+     * Assert that the compact call has not already occured.
+     *
+     * TODO: Will change when we add the OutputStream version of ProtoOutputStream.
+     */
+    private void assertNotCompacted() {
+        if (mCompacted) {
+            throw new IllegalArgumentException("write called after compact");
+        }
+    }
+
+    /**
+     * Finish the encoding of the data, and return a byte[] with
+     * the protobuf formatted data.
+     *
+     * <p>After this call, do not call any of the write* functions. The
+     * behavior is undefined.
+     */
+    public @NonNull byte[] getBytes() {
+        compactIfNecessary();
+
+        return mBuffer.getBytes(mBuffer.getReadableSize());
+    }
+
+    /**
+     * If the buffer hasn't already had the nested object size fields compacted
+     * and turned into an actual protobuf format, then do so.
+     */
+    private void compactIfNecessary() {
+        if (!mCompacted) {
+            if (mDepth != 0) {
+                throw new IllegalArgumentException("Trying to compact with " + mDepth
+                        + " missing calls to endObject");
+            }
+
+            // The buffer must be compacted.
+            mBuffer.startEditing();
+            final int readableSize = mBuffer.getReadableSize();
+
+            // Cache the sizes of the objects
+            editEncodedSize(readableSize);
+
+            // Re-write the buffer with the sizes as proper varints instead
+            // of pairs of uint32s. We know this will always fit in the same
+            // buffer because the pair of uint32s is exactly 8 bytes long, and
+            // the single varint size will be no more than 5 bytes long.
+            mBuffer.rewindRead();
+            compactSizes(readableSize);
+
+            // If there is any data left over that wasn't copied yet, copy it.
+            if (mCopyBegin < readableSize) {
+                mBuffer.writeFromThisBuffer(mCopyBegin, readableSize - mCopyBegin);
+            }
+
+            // Set the new readableSize
+            mBuffer.startEditing();
+
+            // It's not valid to write to this object anymore. The write
+            // pointers are off, and then some of the data would be compacted
+            // and some not.
+            mCompacted = true;
+        }
+    }
+
+    /**
+     * First compaction pass. Iterate through the data, and fill in the
+     * nested object sizes so the next pass can compact them.
+     */
+    private int editEncodedSize(int rawSize) {
+        int objectStart = mBuffer.getReadPos();
+        int objectEnd = objectStart + rawSize;
+        int encodedSize = 0;
+        int tagPos;
+
+        while ((tagPos = mBuffer.getReadPos()) < objectEnd) {
+            int tag = readRawTag();
+            encodedSize += EncodedBuffer.getRawVarint32Size(tag);
+
+            final int wireType = tag & WIRE_TYPE_MASK;
+            switch (wireType) {
+                case WIRE_TYPE_VARINT:
+                    encodedSize++;
+                    while ((mBuffer.readRawByte() & 0x80) != 0) {
+                        encodedSize++;
+                    }
+                    break;
+                case WIRE_TYPE_FIXED64:
+                    encodedSize += 8;
+                    mBuffer.skipRead(8);
+                    break;
+                case WIRE_TYPE_LENGTH_DELIMITED: {
+                    // This object is not of a fixed-size type.  So we need to figure
+                    // out how big it should be.
+                    final int childRawSize = mBuffer.readRawFixed32();
+                    final int childEncodedSizePos = mBuffer.getReadPos();
+                    int childEncodedSize = mBuffer.readRawFixed32();
+                    if (childRawSize >= 0) {
+                        // We know the size, just skip ahead.
+                        if (childEncodedSize != childRawSize) {
+                            throw new RuntimeException("Pre-computed size where the"
+                                    + " precomputed size and the raw size in the buffer"
+                                    + " don't match! childRawSize=" + childRawSize
+                                    + " childEncodedSize=" + childEncodedSize
+                                    + " childEncodedSizePos=" + childEncodedSizePos);
+                        }
+                        mBuffer.skipRead(childRawSize);
+                    } else {
+                        // We need to compute the size.  Recurse.
+                        childEncodedSize = editEncodedSize(-childRawSize);
+                        mBuffer.editRawFixed32(childEncodedSizePos, childEncodedSize);
+                    }
+                    encodedSize += EncodedBuffer.getRawVarint32Size(childEncodedSize)
+                            + childEncodedSize;
+                    break;
+                }
+                case WIRE_TYPE_START_GROUP:
+                case WIRE_TYPE_END_GROUP:
+                    throw new RuntimeException("groups not supported at index " + tagPos);
+                case WIRE_TYPE_FIXED32:
+                    encodedSize += 4;
+                    mBuffer.skipRead(4);
+                    break;
+                default:
+                    throw new ProtoParseException("editEncodedSize Bad tag tag=0x"
+                            + Integer.toHexString(tag) + " wireType=" + wireType
+                            + " -- " + mBuffer.getDebugString());
+            }
+        }
+
+        return encodedSize;
+    }
+
+    /**
+     * Second compaction pass.  Iterate through the data, and copy the data
+     * forward in the buffer, converting the pairs of uint32s into a single
+     * unsigned varint of the size.
+     */
+    private void compactSizes(int rawSize) {
+        int objectStart = mBuffer.getReadPos();
+        int objectEnd = objectStart + rawSize;
+        int tagPos;
+        while ((tagPos = mBuffer.getReadPos()) < objectEnd) {
+            int tag = readRawTag();
+
+            // For all the non-length-delimited field types, just skip over them,
+            // and we'll just System.arraycopy it later, either in the case for
+            // WIRE_TYPE_LENGTH_DELIMITED or at the top of the stack in compactIfNecessary().
+            final int wireType = tag & WIRE_TYPE_MASK;
+            switch (wireType) {
+                case WIRE_TYPE_VARINT:
+                    while ((mBuffer.readRawByte() & 0x80) != 0) { }
+                    break;
+                case WIRE_TYPE_FIXED64:
+                    mBuffer.skipRead(8);
+                    break;
+                case WIRE_TYPE_LENGTH_DELIMITED: {
+                    // Copy everything up to now, including the tag for this field.
+                    mBuffer.writeFromThisBuffer(mCopyBegin, mBuffer.getReadPos() - mCopyBegin);
+                    // Write the new size.
+                    final int childRawSize = mBuffer.readRawFixed32();
+                    final int childEncodedSize = mBuffer.readRawFixed32();
+                    mBuffer.writeRawVarint32(childEncodedSize);
+                    // Next time, start copying from here.
+                    mCopyBegin = mBuffer.getReadPos();
+                    if (childRawSize >= 0) {
+                        // This is raw data, not an object. Skip ahead by the size.
+                        // Recurse into the child
+                        mBuffer.skipRead(childEncodedSize);
+                    } else {
+                        compactSizes(-childRawSize);
+                    }
+                    break;
+                    // TODO: What does regular proto do if the object would be 0 size
+                    // (e.g. if it is all default values).
+                }
+                case WIRE_TYPE_START_GROUP:
+                case WIRE_TYPE_END_GROUP:
+                    throw new RuntimeException("groups not supported at index " + tagPos);
+                case WIRE_TYPE_FIXED32:
+                    mBuffer.skipRead(4);
+                    break;
+                default:
+                    throw new ProtoParseException("compactSizes Bad tag tag=0x"
+                            + Integer.toHexString(tag) + " wireType=" + wireType
+                            + " -- " + mBuffer.getDebugString());
+            }
+        }
+    }
+
+    /**
+     * Write remaining data to the output stream.  If there is no output stream,
+     * this function does nothing. Any currently open objects (i.e. ones that
+     * have not had {@link #end(long)} called for them will not be written).  Whether this
+     * writes objects that are closed if there are remaining open objects is
+     * undefined (current implementation does not write it, future ones will).
+     * For now, can either call {@link #getBytes()} or {@link #flush()}, but not both.
+     */
+    public void flush() {
+        if (mStream == null) {
+            return;
+        }
+        if (mDepth != 0) {
+            // TODO: The compacting code isn't ready yet to compact unless we're done.
+            // TODO: Fix that.
+            return;
+        }
+        if (mCompacted) {
+            // If we're compacted, we already wrote it finished.
+            return;
+        }
+        compactIfNecessary();
+        final byte[] data = mBuffer.getBytes(mBuffer.getReadableSize());
+        try {
+            mStream.write(data);
+            mStream.flush();
+        } catch (IOException ex) {
+            throw new RuntimeException("Error flushing proto to stream", ex);
+        }
+    }
+
+    /**
+     * Read a raw tag from the buffer.
+     */
+    private int readRawTag() {
+        if (mBuffer.getReadPos() == mBuffer.getReadableSize()) {
+            return 0;
+        }
+        return (int)mBuffer.readRawUnsigned();
+    }
+
+    /**
+     * Dump debugging data about the buffers with the given log tag.
+     */
+    public void dump(@NonNull String tag) {
+        Log.d(tag, mBuffer.getDebugString());
+        mBuffer.dumpBuffers(tag);
+    }
+}
diff --git a/android/util/proto/ProtoParseException.java b/android/util/proto/ProtoParseException.java
new file mode 100644
index 0000000..5ba9bf8
--- /dev/null
+++ b/android/util/proto/ProtoParseException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.proto;
+
+import android.annotation.TestApi;
+
+/**
+ * Thrown when there is an error parsing protobuf data.
+ *
+ * @hide
+ */
+@TestApi
+public class ProtoParseException extends RuntimeException {
+
+    /**
+     * Construct a ProtoParseException.
+     *
+     * @param msg The message.
+     */
+    public ProtoParseException(String msg) {
+        super(msg);
+    }
+}
+
diff --git a/android/util/proto/ProtoStream.java b/android/util/proto/ProtoStream.java
new file mode 100644
index 0000000..1940da9
--- /dev/null
+++ b/android/util/proto/ProtoStream.java
@@ -0,0 +1,636 @@
+/*
+ * Copyright (C) 2018 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.proto;
+
+import android.annotation.IntDef;
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base utility class for protobuf streams.
+ *
+ * Contains a set of constants and methods used in generated code for
+ * {@link ProtoOutputStream}.
+ *
+ * @hide
+ */
+public class ProtoStream {
+
+    /**
+     * A protobuf wire type.  All application-level types are represented using
+     * varint, fixed64, length-delimited and fixed32 wire types. The start-group
+     * and end-group types are unused in modern protobuf versions (proto2 and proto3),
+     * but are included here for completeness.
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        WIRE_TYPE_VARINT,
+        WIRE_TYPE_FIXED64,
+        WIRE_TYPE_LENGTH_DELIMITED,
+        WIRE_TYPE_START_GROUP,
+        WIRE_TYPE_END_GROUP,
+        WIRE_TYPE_FIXED32
+    })
+    public @interface WireType {}
+
+    /**
+     * Application-level protobuf field types, as would be used in a .proto file.
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef({
+        FIELD_TYPE_UNKNOWN,
+        FIELD_TYPE_DOUBLE,
+        FIELD_TYPE_FLOAT,
+        FIELD_TYPE_INT64,
+        FIELD_TYPE_UINT64,
+        FIELD_TYPE_INT32,
+        FIELD_TYPE_FIXED64,
+        FIELD_TYPE_FIXED32,
+        FIELD_TYPE_BOOL,
+        FIELD_TYPE_STRING,
+        FIELD_TYPE_MESSAGE,
+        FIELD_TYPE_BYTES,
+        FIELD_TYPE_UINT32,
+        FIELD_TYPE_ENUM,
+        FIELD_TYPE_SFIXED32,
+        FIELD_TYPE_SFIXED64,
+        FIELD_TYPE_SINT32,
+        FIELD_TYPE_SINT64,
+    })
+    public @interface FieldType {}
+
+
+    /**
+     * Represents the cardinality of a protobuf field.
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef({
+        FIELD_COUNT_UNKNOWN,
+        FIELD_COUNT_SINGLE,
+        FIELD_COUNT_REPEATED,
+        FIELD_COUNT_PACKED,
+    })
+    public @interface FieldCount {}
+
+    /**
+     * Number of bits to shift the field number to form a tag.
+     *
+     * <pre>
+     * // Reading a field number from a tag.
+     * int fieldNumber = tag &gt;&gt;&gt; FIELD_ID_SHIFT;
+     *
+     * // Building a tag from a field number and a wire type.
+     * int tag = (fieldNumber &lt;&lt; FIELD_ID_SHIFT) | wireType;
+     * </pre>
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final int FIELD_ID_SHIFT = 3;
+
+    /**
+     * Mask to select the wire type from a tag.
+     *
+     * <pre>
+     * // Reading a wire type from a tag.
+     * int wireType = tag &amp; WIRE_TYPE_MASK;
+     *
+     * // Building a tag from a field number and a wire type.
+     * int tag = (fieldNumber &lt;&lt; FIELD_ID_SHIFT) | wireType;
+     * </pre>
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final int WIRE_TYPE_MASK = (1 << FIELD_ID_SHIFT) - 1;
+
+    /**
+     * Mask to select the field id from a tag.
+     * @hide (not used by anything, and not actually useful, because you also want
+     * to shift when you mask the field id).
+     */
+    public static final int FIELD_ID_MASK = ~WIRE_TYPE_MASK;
+
+    /**
+     * Varint wire type code.
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final int WIRE_TYPE_VARINT = 0;
+
+    /**
+     * Fixed64 wire type code.
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final int WIRE_TYPE_FIXED64 = 1;
+
+    /**
+     * Length delimited wire type code.
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final int WIRE_TYPE_LENGTH_DELIMITED = 2;
+
+    /**
+     * Start group wire type code.
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final int WIRE_TYPE_START_GROUP = 3;
+
+    /**
+     * End group wire type code.
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final int WIRE_TYPE_END_GROUP = 4;
+
+    /**
+     * Fixed32 wire type code.
+     *
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final int WIRE_TYPE_FIXED32 = 5;
+
+    /**
+     * Position of the field type in a (long) fieldId.
+     */
+    public static final int FIELD_TYPE_SHIFT = 32;
+
+    /**
+     * Mask for the field types stored in a fieldId.  Leaves a whole
+     * byte for future expansion, even though there are currently only 17 types.
+     */
+    public static final long FIELD_TYPE_MASK = 0x0ffL << FIELD_TYPE_SHIFT;
+
+    /**
+     * Not a real field type.
+     * @hide
+     */
+    public static final long FIELD_TYPE_UNKNOWN = 0;
+
+
+    /*
+     * The FIELD_TYPE_ constants are copied from
+     * external/protobuf/src/google/protobuf/descriptor.h directly, so no
+     * extra mapping needs to be maintained in this case.
+     */
+
+    /**
+     * Field type code for double fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, double)
+     * ProtoOutputStream.write(long, double)} method.
+     */
+    public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for float fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, float)
+     * ProtoOutputStream.write(long, float)} method.
+     */
+    public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for int64 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, long)
+     * ProtoOutputStream.write(long, long)} method.
+     */
+    public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for uint64 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, long)
+     * ProtoOutputStream.write(long, long)} method.
+     */
+    public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for int32 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, int)
+     * ProtoOutputStream.write(long, int)} method.
+     */
+    public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for fixed64 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, long)
+     * ProtoOutputStream.write(long, long)} method.
+     */
+    public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for fixed32 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, int)
+     * ProtoOutputStream.write(long, int)} method.
+     */
+
+    /**
+     * Field type code for fixed32 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, int)
+     * ProtoOutputStream.write(long, int)} method.
+     */
+    public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for bool fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, boolean)
+     * ProtoOutputStream.write(long, boolean)} method.
+     */
+    public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for string fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, String)
+     * ProtoOutputStream.write(long, String)} method.
+     */
+    public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT;
+
+    //  public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated.
+
+    /**
+     * Field type code for message fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#start(long)
+     * ProtoOutputStream.start(long)} method.
+     */
+    public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for bytes fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, byte[])
+     * ProtoOutputStream.write(long, byte[])} method.
+     */
+    public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for uint32 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, int)
+     * ProtoOutputStream.write(long, int)} method.
+     */
+    public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for enum fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, int)
+     * ProtoOutputStream.write(long, int)} method.
+     */
+    public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for sfixed32 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, int)
+     * ProtoOutputStream.write(long, int)} method.
+     */
+    public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for sfixed64 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, long)
+     * ProtoOutputStream.write(long, long)} method.
+     */
+    public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for sint32 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, int)
+     * ProtoOutputStream.write(long, int)} method.
+     */
+    public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT;
+
+    /**
+     * Field type code for sint64 fields. Used to build constants in generated
+     * code for use with the {@link ProtoOutputStream#write(long, long)
+     * ProtoOutputStream.write(long, long)} method.
+     */
+    public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT;
+
+    private static final @NonNull String[] FIELD_TYPE_NAMES = new String[]{
+            "Double",
+            "Float",
+            "Int64",
+            "UInt64",
+            "Int32",
+            "Fixed64",
+            "Fixed32",
+            "Bool",
+            "String",
+            "Group",  // This field is deprecated but reserved here for indexing.
+            "Message",
+            "Bytes",
+            "UInt32",
+            "Enum",
+            "SFixed32",
+            "SFixed64",
+            "SInt32",
+            "SInt64",
+    };
+
+    //
+    // FieldId flags for whether the field is single, repeated or packed.
+    //
+    /**
+     * Bit offset for building a field id to be used with a
+     * <code>{@link ProtoOutputStream}.write(...)</code>.
+     *
+     * @see #FIELD_COUNT_MASK
+     * @see #FIELD_COUNT_UNKNOWN
+     * @see #FIELD_COUNT_SINGLE
+     * @see #FIELD_COUNT_REPEATED
+     * @see #FIELD_COUNT_PACKED
+     */
+    public static final int FIELD_COUNT_SHIFT = 40;
+
+    /**
+     * Bit mask for selecting the field count when reading a field id that
+     * is used with a <code>{@link ProtoOutputStream}.write(...)</code> method.
+     *
+     * @see #FIELD_COUNT_SHIFT
+     * @see #FIELD_COUNT_MASK
+     * @see #FIELD_COUNT_UNKNOWN
+     * @see #FIELD_COUNT_SINGLE
+     * @see #FIELD_COUNT_REPEATED
+     * @see #FIELD_COUNT_PACKED
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final long FIELD_COUNT_MASK = 0x0fL << FIELD_COUNT_SHIFT;
+
+    /**
+     * Unknown field count, encoded into a field id used with a
+     * <code>{@link ProtoOutputStream}.write(...)</code> method.
+     *
+     * @see #FIELD_COUNT_SHIFT
+     * @see #FIELD_COUNT_MASK
+     * @see #FIELD_COUNT_SINGLE
+     * @see #FIELD_COUNT_REPEATED
+     * @see #FIELD_COUNT_PACKED
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final long FIELD_COUNT_UNKNOWN = 0;
+
+    /**
+     * Single field count, encoded into a field id used with a
+     * <code>{@link ProtoOutputStream}.write(...)</code> method.
+     *
+     * @see #FIELD_COUNT_SHIFT
+     * @see #FIELD_COUNT_MASK
+     * @see #FIELD_COUNT_UNKNOWN
+     * @see #FIELD_COUNT_REPEATED
+     * @see #FIELD_COUNT_PACKED
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final long FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
+
+    /**
+     * Repeated field count, encoded into a field id used with a
+     * <code>{@link ProtoOutputStream}.write(...)</code> method.
+     *
+     * @see #FIELD_COUNT_SHIFT
+     * @see #FIELD_COUNT_MASK
+     * @see #FIELD_COUNT_UNKNOWN
+     * @see #FIELD_COUNT_SINGLE
+     * @see #FIELD_COUNT_PACKED
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final long FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
+
+    /**
+     * Repeated packed field count, encoded into a field id used with a
+     * <code>{@link ProtoOutputStream}.write(...)</code> method.
+     *
+     * @see #FIELD_COUNT_SHIFT
+     * @see #FIELD_COUNT_MASK
+     * @see #FIELD_COUNT_UNKNOWN
+     * @see #FIELD_COUNT_SINGLE
+     * @see #FIELD_COUNT_REPEATED
+     * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+     * Encoding</a>
+     */
+    public static final long FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
+
+
+    /**
+     * Get the developer-usable name of a field type.
+     */
+    public static @Nullable String getFieldTypeString(@FieldType long fieldType) {
+        int index = ((int) ((fieldType & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) - 1;
+        if (index >= 0 && index < FIELD_TYPE_NAMES.length) {
+            return FIELD_TYPE_NAMES[index];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the developer-usable name of a field count.
+     */
+    public static @Nullable String getFieldCountString(long fieldCount) {
+        if (fieldCount == FIELD_COUNT_SINGLE) {
+            return "";
+        } else if (fieldCount == FIELD_COUNT_REPEATED) {
+            return "Repeated";
+        } else if (fieldCount == FIELD_COUNT_PACKED) {
+            return "Packed";
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the developer-usable name of a wire type.
+     */
+    public static @Nullable String getWireTypeString(@WireType int wireType) {
+        switch (wireType) {
+            case WIRE_TYPE_VARINT:
+                return "Varint";
+            case WIRE_TYPE_FIXED64:
+                return "Fixed64";
+            case WIRE_TYPE_LENGTH_DELIMITED:
+                return "Length Delimited";
+            case WIRE_TYPE_START_GROUP:
+                return "Start Group";
+            case WIRE_TYPE_END_GROUP:
+                return "End Group";
+            case WIRE_TYPE_FIXED32:
+                return "Fixed32";
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Get a debug string for a fieldId.
+     */
+    public static @NonNull String getFieldIdString(long fieldId) {
+        final long fieldCount = fieldId & FIELD_COUNT_MASK;
+        String countString = getFieldCountString(fieldCount);
+        if (countString == null) {
+            countString = "fieldCount=" + fieldCount;
+        }
+        if (countString.length() > 0) {
+            countString += " ";
+        }
+
+        final long fieldType = fieldId & FIELD_TYPE_MASK;
+        String typeString = getFieldTypeString(fieldType);
+        if (typeString == null) {
+            typeString = "fieldType=" + fieldType;
+        }
+
+        return countString + typeString + " tag=" + ((int) fieldId)
+                + " fieldId=0x" + Long.toHexString(fieldId);
+    }
+
+    /**
+     * Combine a fieldId (the field keys in the proto file) and the field flags.
+     * Mostly useful for testing because the generated code contains the fieldId
+     * constants.
+     */
+    public static long makeFieldId(int id, long fieldFlags) {
+        return fieldFlags | (((long) id) & 0x0ffffffffL);
+    }
+
+    //
+    // Child objects
+    //
+
+    /**
+     * Make a token.
+     * Bits 61-63 - tag size (So we can go backwards later if the object had not data)
+     *            - 3 bits, max value 7, max value needed 5
+     * Bit  60    - true if the object is repeated (lets us require endObject or endRepeatedObject)
+     * Bits 59-51 - depth (For error checking)
+     *            - 9 bits, max value 512, when checking, value is masked (if we really
+     *              are more than 512 levels deep)
+     * Bits 32-50 - objectId (For error checking)
+     *            - 19 bits, max value 524,288. that's a lot of objects. IDs will wrap
+     *              because of the overflow, and only the tokens are compared.
+     * Bits  0-31 - offset of interest for the object.
+     */
+    public static long makeToken(int tagSize, boolean repeated, int depth, int objectId,
+            int offset) {
+        return ((0x07L & (long) tagSize) << 61)
+                | (repeated ? (1L << 60) : 0)
+                | (0x01ffL & (long) depth) << 51
+                | (0x07ffffL & (long) objectId) << 32
+                | (0x0ffffffffL & (long) offset);
+    }
+
+    /**
+     * Get the encoded tag size from the token.
+     *
+     * @hide
+     */
+    public static int getTagSizeFromToken(long token) {
+        return (int) (0x7 & (token >> 61));
+    }
+
+    /**
+     * Get whether the token has the repeated bit set to true or false
+     *
+     * @hide
+     */
+    public static boolean getRepeatedFromToken(long token) {
+        return (0x1 & (token >> 60)) != 0;
+    }
+
+    /**
+     * Get the nesting depth from the token.
+     *
+     * @hide
+     */
+    public static int getDepthFromToken(long token) {
+        return (int) (0x01ff & (token >> 51));
+    }
+
+    /**
+     * Get the object ID from the token.
+     *
+     * <p>The object ID is a serial number for the
+     * startObject calls that have happened on this object.  The values are truncated
+     * to 9 bits, but that is sufficient for error checking.
+     *
+     * @hide
+     */
+    public static int getObjectIdFromToken(long token) {
+        return (int) (0x07ffff & (token >> 32));
+    }
+
+    /**
+     * Get the location of the offset recorded in the token.
+     *
+     * @hide
+     */
+    public static int getOffsetFromToken(long token) {
+        return (int) token;
+    }
+
+    /**
+     * Convert the object ID to the ordinal value -- the n-th call to startObject.
+     *
+     * <p>The object IDs start at -1 and count backwards, so that the value is unlikely
+     * to alias with an actual size field that had been written.
+     *
+     * @hide
+     */
+    public static int convertObjectIdToOrdinal(int objectId) {
+        return (-1 & 0x07ffff) - objectId;
+    }
+
+    /**
+     * Return a debugging string of a token.
+     */
+    public static @NonNull String token2String(long token) {
+        if (token == 0L) {
+            return "Token(0)";
+        } else {
+            return "Token(val=0x" + Long.toHexString(token)
+                    + " depth=" + getDepthFromToken(token)
+                    + " object=" + convertObjectIdToOrdinal(getObjectIdFromToken(token))
+                    + " tagSize=" + getTagSizeFromToken(token)
+                    + " offset=" + getOffsetFromToken(token)
+                    + ')';
+        }
+    }
+
+    /**
+     * @hide
+     */
+    protected ProtoStream() {}
+}
diff --git a/android/util/proto/ProtoUtils.java b/android/util/proto/ProtoUtils.java
new file mode 100644
index 0000000..8464d2d
--- /dev/null
+++ b/android/util/proto/ProtoUtils.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 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.proto;
+
+import android.util.AggStats;
+import android.util.Duration;
+
+import java.io.IOException;
+
+/**
+ * This class contains a list of helper functions to write common proto in
+ * //frameworks/base/core/proto/android/base directory
+ * @hide
+ */
+public class ProtoUtils {
+
+    /**
+     * Dump AggStats to ProtoOutputStream
+     */
+    public static void toAggStatsProto(ProtoOutputStream proto, long fieldId,
+            long min, long average, long max, int meanKb, int maxKb) {
+        final long aggStatsToken = proto.start(fieldId);
+        proto.write(AggStats.MIN, min);
+        proto.write(AggStats.AVERAGE, average);
+        proto.write(AggStats.MAX, max);
+        proto.write(AggStats.MEAN_KB, meanKb);
+        proto.write(AggStats.MAX_KB, maxKb);
+        proto.end(aggStatsToken);
+    }
+
+    /**
+     * Dump AggStats to ProtoOutputStream
+     */
+    public static void toAggStatsProto(ProtoOutputStream proto, long fieldId,
+            long min, long average, long max) {
+        toAggStatsProto(proto, fieldId, min, average, max, 0, 0);
+    }
+
+    /**
+     * Dump Duration to ProtoOutputStream
+     */
+    public static void toDuration(ProtoOutputStream proto, long fieldId, long startMs, long endMs) {
+        final long token = proto.start(fieldId);
+        proto.write(Duration.START_MS, startMs);
+        proto.write(Duration.END_MS, endMs);
+        proto.end(token);
+    }
+
+    /**
+     * Helper function to write bit-wise flags to proto as repeated enums
+     */
+    public static void writeBitWiseFlagsToProtoEnum(ProtoOutputStream proto, long fieldId,
+            int flags, int[] origEnums, int[] protoEnums) {
+        if (protoEnums.length != origEnums.length) {
+            throw new IllegalArgumentException("The length of origEnums must match protoEnums");
+        }
+        int len = origEnums.length;
+        for (int i = 0; i < len; i++) {
+            // handle zero flag case.
+            if (origEnums[i] == 0 && flags == 0) {
+                proto.write(fieldId, protoEnums[i]);
+                return;
+            }
+            if ((flags & origEnums[i]) != 0) {
+                proto.write(fieldId, protoEnums[i]);
+            }
+        }
+    }
+
+    /**
+     * Provide debug data about the current field as a string
+     */
+    public static String currentFieldToString(ProtoInputStream proto) throws IOException {
+        StringBuilder sb = new StringBuilder();
+
+        final int fieldNumber = proto.getFieldNumber();
+        final int wireType = proto.getWireType();
+        long fieldConstant;
+
+        sb.append("Offset : 0x" + Integer.toHexString(proto.getOffset()));
+        sb.append("\nField Number : 0x" + Integer.toHexString(proto.getFieldNumber()));
+        sb.append("\nWire Type : ");
+        switch (wireType) {
+            case ProtoStream.WIRE_TYPE_VARINT:
+                sb.append("varint");
+                fieldConstant = ProtoStream.makeFieldId(fieldNumber,
+                        ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64);
+                sb.append("\nField Value : 0x" + Long.toHexString(proto.readLong(fieldConstant)));
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED64:
+                sb.append("fixed64");
+                fieldConstant = ProtoStream.makeFieldId(fieldNumber,
+                        ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64);
+                sb.append("\nField Value : 0x" + Long.toHexString(proto.readLong(fieldConstant)));
+                break;
+            case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+                sb.append("length delimited");
+                fieldConstant = ProtoStream.makeFieldId(fieldNumber,
+                        ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES);
+                sb.append("\nField Bytes : " + proto.readBytes(fieldConstant));
+                break;
+            case ProtoStream.WIRE_TYPE_START_GROUP:
+                sb.append("start group");
+                break;
+            case ProtoStream.WIRE_TYPE_END_GROUP:
+                sb.append("end group");
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED32:
+                sb.append("fixed32");
+                fieldConstant = ProtoStream.makeFieldId(fieldNumber,
+                        ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32);
+                sb.append("\nField Value : 0x" + Integer.toHexString(proto.readInt(fieldConstant)));
+                break;
+            default:
+                sb.append("unknown(" + proto.getWireType() + ")");
+        }
+        return sb.toString();
+    }
+}
diff --git a/android/util/proto/WireTypeMismatchException.java b/android/util/proto/WireTypeMismatchException.java
new file mode 100644
index 0000000..d90b4f8
--- /dev/null
+++ b/android/util/proto/WireTypeMismatchException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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.proto;
+
+import android.annotation.TestApi;
+
+/**
+ * Thrown when there is an error parsing protobuf data.
+ *
+ * @hide
+ */
+@TestApi
+public class WireTypeMismatchException extends ProtoParseException {
+
+    /**
+     * Construct a WireTypeMismatchException.
+     *
+     * @param msg The message.
+     */
+    public WireTypeMismatchException(String msg) {
+        super(msg);
+    }
+}
+