Open XML and parse binary XML resource files.
diff --git a/resources/src/main/java/org/robolectric/res/android/Asset.java b/resources/src/main/java/org/robolectric/res/android/Asset.java
index a417295..e03e4c7 100644
--- a/resources/src/main/java/org/robolectric/res/android/Asset.java
+++ b/resources/src/main/java/org/robolectric/res/android/Asset.java
@@ -4,133 +4,28 @@
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.RandomAccessFile;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import org.robolectric.res.android.ZipFileRO.ZipEntryRO;
 
-/**
- * Class providing access to a read-only asset.  Asset objects are NOT thread-safe, and should not
- * be shared across threads.
- *
- * transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/include/androidfw/Asset.h
- * and https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/libs/androidfw/Asset.cpp
- *
- * Instances of this class provide read-only operations on a byte stream.
- * Access may
- * be optimized for streaming, random, or whole buffer modes.  All operations are supported
- * regardless of how the file was opened, but some things will be less efficient.  [pass that
- * in??]. "Asset" is the base class for all types of assets.  The classes below
- * provide most of the implementation.  The AssetManager uses one of the static "create"
- * functions defined here to create a new instance.
- */
-public class Asset {
+public abstract class Asset {
 
-  private final RandomAccessFile f;
-
-  public Asset(RandomAccessFile f) {
-    this.f = f;
-  }
-
-  public long size() {
-    try {
-      return f.length();
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  public int read() {
-    try {
-      return f.read();
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  public int read(byte[] b, int off, int len) {
-    try {
-      return f.read(b, off, len);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  public long seek(long offset, int whence) {
-    throw new UnsupportedOperationException("not yet implemented");
-//    if (whence > 0) {
-//      SEEK_END
-//    } else if (whence < 0) {
-//      SEEK_SET
-//    } else {
-//      SEEK_CUR
-//    }
-//    return f.seek();
-  }
-  //
-
-  //      public:
-//      virtual ~Asset(void) = default;
-//      static int32_t getGlobalCount();
-//      static String8 getAssetAllocations();
-//
-//    /* used when opening an asset */
-  public enum AccessMode {
-    ACCESS_UNKNOWN,
-    /* read chunks, and seek forward and backward */
-    ACCESS_RANDOM,
-    /* read sequentially, with an occasional forward seek */
-    ACCESS_STREAMING,
-    /* caller plans to ask for a read-only buffer with all data */
-    ACCESS_BUFFER,
-  }
-
-//using namespace android;
+  //using namespace android;
 //#ifndef O_BINARY
 //# define O_BINARY 0
 //#endif
 private static final boolean kIsDebug = false;
-private static final Object gAssetLock = new Object();
-private static List<Asset> gAssets = new LinkedList<>();
-
+  private static final Object gAssetLock = new Object();
+  private static List<Asset> gAssets = new LinkedList<>();
+  public Runnable onClose;
   private String8    mAssetSource;       // debug string
 
-  /* set the asset source string */
-  void setAssetSource(String8 path) {
-    mAssetSource = path;
-  }
-
-protected void registerAsset(Asset asset)
-{
-  int gCount = 0;
-    synchronized (gAssetLock) {
-      gAssets.add(asset);
-      gCount = gAssets.size();
-    }
-  if (kIsDebug) {
-    ALOGI("Creating Asset %p #%d\n", asset, gCount);
-  }
-
-}
-
-protected void unregisterAsset(Asset asset) {
-  int gCount = 0;
-  synchronized (gAssetLock) {
-    gAssets.remove(asset);
-    gCount = gAssets.size();
-  }
-  if (kIsDebug) {
-        ALOGI("Destroying Asset in %p #%d\n", asset, gCount);
-    }
-}
-
-protected int getGlobalCount()
-{
-  synchronized (gAssetLock) {
-    return gAssets.size();
-  }
-}
-
-//String8 Asset::getAssetAllocations()
+  //String8 Asset::getAssetAllocations()
 //{
 //    AutoMutex _l(gAssetLock);
 //    String8 res;
@@ -202,662 +97,511 @@
 //    return pAsset;
   try {
     RandomAccessFile f = new RandomAccessFile(fileName, "r");
-    return new Asset(f);
+    return new FileAsset(f);
   } catch (FileNotFoundException e) {
     throw new RuntimeException(e);
   }
 }
 
-/*
- * Create a new Asset from a compressed file on disk.  There is a fair chance
- * that the file doesn't actually exist.
- *
- * We currently support gzip files.  We might want to handle .bz2 someday.
- */
-
-static Asset createFromCompressedFile(String fileName,
-    AccessMode mode)
-{
-  return null;
-//    _CompressedAsset pAsset;
-//    status_t result;
-//    off64_t fileLen;
-//    bool scanResult;
-//    long offset;
-//    int method;
-//    long uncompressedLen, compressedLen;
-//    int fd;
-//    fd = open(fileName, O_RDONLY | O_BINARY);
-//    if (fd < 0)
-//        return NULL;
-//    fileLen = lseek(fd, 0, SEEK_END);
-//    if (fileLen < 0) {
-//        ::close(fd);
-//        return NULL;
-//    }
-//    (void) lseek(fd, 0, SEEK_SET);
-//    /* want buffered I/O for the file scan; must dup so fclose() is safe */
-//    FILE* fp = fdopen(dup(fd), "rb");
-//    if (fp == NULL) {
-//        ::close(fd);
-//        return NULL;
-//    }
-//    unsigned long crc32;
-//    scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
-//                    &compressedLen, &crc32);
-//    offset = ftell(fp);
-//    fclose(fp);
-//    if (!scanResult) {
-//        ALOGD("File '%s' is not in gzip format\n", fileName);
-//        ::close(fd);
-//        return NULL;
-//    }
-//    pAsset = new _CompressedAsset;
-//    result = pAsset->openChunk(fd, offset, method, uncompressedLen,
-//                compressedLen);
-//    if (result != NO_ERROR) {
-//        delete pAsset;
-//        return NULL;
-//    }
-//    pAsset->mAccessMode = mode;
-//    return pAsset;
-}
-//#if 0
-///*
-// * Create a new Asset from part of an open file.
-// */
-///*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
-//    size_t length, AccessMode mode)
-//{
-//    _FileAsset* pAsset;
-//    status_t result;
-//    pAsset = new _FileAsset;
-//    result = pAsset->openChunk(NULL, fd, offset, length);
-//    if (result != NO_ERROR)
-//        return NULL;
-//    pAsset->mAccessMode = mode;
-//    return pAsset;
-//}
-///*
-// * Create a new Asset from compressed data in an open file.
-// */
-///*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
-//    int compressionMethod, size_t uncompressedLen, size_t compressedLen,
-//    AccessMode mode)
-//{
-//    _CompressedAsset* pAsset;
-//    status_t result;
-//    pAsset = new _CompressedAsset;
-//    result = pAsset->openChunk(fd, offset, compressionMethod,
-//                uncompressedLen, compressedLen);
-//    if (result != NO_ERROR)
-//        return NULL;
-//    pAsset->mAccessMode = mode;
-//    return pAsset;
-//}
-//#endif
-///*
-// * Create a new Asset from a memory mapping.
-// */
-///*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
-//    AccessMode mode)
-//{
-//    _FileAsset* pAsset;
-//    status_t result;
-//    pAsset = new _FileAsset;
-//    result = pAsset->openChunk(dataMap);
-//    if (result != NO_ERROR)
-//        return NULL;
-//    pAsset->mAccessMode = mode;
-//    return pAsset;
-//}
-///*
-// * Create a new Asset from compressed data in a memory mapping.
-// */
-///*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
-//    size_t uncompressedLen, AccessMode mode)
-//{
-//    _CompressedAsset* pAsset;
-//    status_t result;
-//    pAsset = new _CompressedAsset;
-//    result = pAsset->openChunk(dataMap, uncompressedLen);
-//    if (result != NO_ERROR)
-//        return NULL;
-//    pAsset->mAccessMode = mode;
-//    return pAsset;
-//}
-///*
-// * Do generic seek() housekeeping.  Pass in the offset/whence values from
-// * the seek request, along with the current chunk offset and the chunk
-// * length.
-// *
-// * Returns the new chunk offset, or -1 if the seek is illegal.
-// */
-//long handleSeek(long offset, int whence, long curPosn, long maxPosn)
-//{
-//    long newOffset;
-//    switch (whence) {
-//    case SEEK_SET:
-//        newOffset = offset;
-//        break;
-//    case SEEK_CUR:
-//        newOffset = curPosn + offset;
-//        break;
-//    case SEEK_END:
-//        newOffset = maxPosn + offset;
-//        break;
-//    default:
-//        ALOGW("unexpected whence %d\n", whence);
-//        // this was happening due to an off64_t size mismatch
-//        assert(false);
-//        return (off64_t) -1;
-//    }
-//    if (newOffset < 0 || newOffset > maxPosn) {
-//        ALOGW("seek out of range: want %ld, end=%ld\n",
-//            (long) newOffset, (long) maxPosn);
-//        return (off64_t) -1;
-//    }
-//    return newOffset;
-//}
-///*
-// * ===========================================================================
-// *      _FileAsset
-// * ===========================================================================
-// */
-///*
-// * Constructor.
-// */
-//_FileAsset::_FileAsset(void)
-//    : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
-//{
-//    // Register the Asset with the global list here after it is fully constructed and its
-//    // vtable pointer points to this concrete type. b/31113965
-//    registerAsset(this);
-//}
-///*
-// * Destructor.  Release resources.
-// */
-//_FileAsset::~_FileAsset(void)
-//{
-//    close();
-//    // Unregister the Asset from the global list here before it is destructed and while its vtable
-//    // pointer still points to this concrete type. b/31113965
-//    unregisterAsset(this);
-//}
-///*
-// * Operate on a chunk of an uncompressed file.
-// *
-// * Zero-length chunks are allowed.
-// */
-//status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
-//{
-//    assert(mFp == NULL);    // no reopen
-//    assert(mMap == NULL);
-//    assert(fd >= 0);
-//    assert(offset >= 0);
-//    /*
-//     * Seek to end to get file length.
-//     */
-//    off64_t fileLength;
-//    fileLength = lseek64(fd, 0, SEEK_END);
-//    if (fileLength == (off64_t) -1) {
-//        // probably a bad file descriptor
-//        ALOGD("failed lseek (errno=%d)\n", errno);
-//        return UNKNOWN_ERROR;
-//    }
-//    if ((off64_t) (offset + length) > fileLength) {
-//        ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
-//            (long) offset, (long) length, (long) fileLength);
-//        return BAD_INDEX;
-//    }
-//    /* after fdopen, the fd will be closed on fclose() */
-//    mFp = fdopen(fd, "rb");
-//    if (mFp == NULL)
-//        return UNKNOWN_ERROR;
-//    mStart = offset;
-//    mLength = length;
-//    assert(mOffset == 0);
-//    /* seek the FILE* to the start of chunk */
-//    if (fseek(mFp, mStart, SEEK_SET) != 0) {
-//        assert(false);
-//    }
-//    mFileName = fileName != NULL ? strdup(fileName) : NULL;
-//    return NO_ERROR;
-//}
-///*
-// * Create the chunk from the map.
-// */
-//status_t _FileAsset::openChunk(FileMap* dataMap)
-//{
-//    assert(mFp == NULL);    // no reopen
-//    assert(mMap == NULL);
-//    assert(dataMap != NULL);
-//    mMap = dataMap;
-//    mStart = -1;            // not used
-//    mLength = dataMap->getDataLength();
-//    assert(mOffset == 0);
-//    return NO_ERROR;
-//}
-///*
-// * Read a chunk of data.
-// */
-//ssize_t _FileAsset::read(void* buf, size_t count)
-//{
-//    size_t maxLen;
-//    size_t actual;
-//    assert(mOffset >= 0 && mOffset <= mLength);
-//    if (getAccessMode() == ACCESS_BUFFER) {
-//        /*
-//         * On first access, read or map the entire file.  The caller has
-//         * requested buffer access, either because they're going to be
-//         * using the buffer or because what they're doing has appropriate
-//         * performance needs and access patterns.
-//         */
-//        if (mBuf == NULL)
-//            getBuffer(false);
-//    }
-//    /* adjust count if we're near EOF */
-//    maxLen = mLength - mOffset;
-//    if (count > maxLen)
-//        count = maxLen;
-//    if (!count)
-//        return 0;
-//    if (mMap != NULL) {
-//        /* copy from mapped area */
-//        //printf("map read\n");
-//        memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
-//        actual = count;
-//    } else if (mBuf != NULL) {
-//        /* copy from buffer */
-//        //printf("buf read\n");
-//        memcpy(buf, (char*)mBuf + mOffset, count);
-//        actual = count;
-//    } else {
-//        /* read from the file */
-//        //printf("file read\n");
-//        if (ftell(mFp) != mStart + mOffset) {
-//            ALOGE("Hosed: %ld != %ld+%ld\n",
-//                ftell(mFp), (long) mStart, (long) mOffset);
-//            assert(false);
-//        }
-//        /*
-//         * This returns 0 on error or eof.  We need to use ferror() or feof()
-//         * to tell the difference, but we don't currently have those on the
-//         * device.  However, we know how much data is *supposed* to be in the
-//         * file, so if we don't read the full amount we know something is
-//         * hosed.
-//         */
-//        actual = fread(buf, 1, count, mFp);
-//        if (actual == 0)        // something failed -- I/O error?
-//            return -1;
-//        assert(actual == count);
-//    }
-//    mOffset += actual;
-//    return actual;
-//}
-///*
-// * Seek to a new position.
-// */
-//off64_t _FileAsset::seek(off64_t offset, int whence)
-//{
-//    off64_t newPosn;
-//    off64_t actualOffset;
-//    // compute new position within chunk
-//    newPosn = handleSeek(offset, whence, mOffset, mLength);
-//    if (newPosn == (off64_t) -1)
-//        return newPosn;
-//    actualOffset = mStart + newPosn;
-//    if (mFp != NULL) {
-//        if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
-//            return (off64_t) -1;
-//    }
-//    mOffset = actualOffset - mStart;
-//    return mOffset;
-//}
-///*
-// * Close the asset.
-// */
-//void _FileAsset::close(void)
-//{
-//    if (mMap != NULL) {
-//        delete mMap;
-//        mMap = NULL;
-//    }
-//    if (mBuf != NULL) {
-//        delete[] mBuf;
-//        mBuf = NULL;
-//    }
-//    if (mFileName != NULL) {
-//        free(mFileName);
-//        mFileName = NULL;
-//    }
-//    if (mFp != NULL) {
-//        // can only be NULL when called from destructor
-//        // (otherwise we would never return this object)
-//        fclose(mFp);
-//        mFp = NULL;
-//    }
-//}
-///*
-// * Return a read-only pointer to a buffer.
-// *
-// * We can either read the whole thing in or map the relevant piece of
-// * the source file.  Ideally a map would be established at a higher
-// * level and we'd be using a different object, but we didn't, so we
-// * deal with it here.
-// */
-//const void* _FileAsset::getBuffer(bool wordAligned)
-//{
-//    /* subsequent requests just use what we did previously */
-//    if (mBuf != NULL)
-//        return mBuf;
-//    if (mMap != NULL) {
-//        if (!wordAligned) {
-//            return  mMap->getDataPtr();
-//        }
-//        return ensureAlignment(mMap);
-//    }
-//    assert(mFp != NULL);
-//    if (mLength < kReadVsMapThreshold) {
-//        unsigned char* buf;
-//        long allocLen;
-//        /* zero-length files are allowed; not sure about zero-len allocs */
-//        /* (works fine with gcc + x86linux) */
-//        allocLen = mLength;
-//        if (mLength == 0)
-//            allocLen = 1;
-//        buf = new unsigned char[allocLen];
-//        if (buf == NULL) {
-//            ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
-//            return NULL;
-//        }
-//        ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
-//        if (mLength > 0) {
-//            long oldPosn = ftell(mFp);
-//            fseek(mFp, mStart, SEEK_SET);
-//            if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
-//                ALOGE("failed reading %ld bytes\n", (long) mLength);
-//                delete[] buf;
-//                return NULL;
-//            }
-//            fseek(mFp, oldPosn, SEEK_SET);
-//        }
-//        ALOGV(" getBuffer: loaded into buffer\n");
-//        mBuf = buf;
-//        return mBuf;
-//    } else {
-//        FileMap* map;
-//        map = new FileMap;
-//        if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
-//            delete map;
-//            return NULL;
-//        }
-//        ALOGV(" getBuffer: mapped\n");
-//        mMap = map;
-//        if (!wordAligned) {
-//            return  mMap->getDataPtr();
-//        }
-//        return ensureAlignment(mMap);
-//    }
-//}
-//int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
-//{
-//    if (mMap != NULL) {
-//        const char* fname = mMap->getFileName();
-//        if (fname == NULL) {
-//            fname = mFileName;
-//        }
-//        if (fname == NULL) {
-//            return -1;
-//        }
-//        *outStart = mMap->getDataOffset();
-//        *outLength = mMap->getDataLength();
-//        return open(fname, O_RDONLY | O_BINARY);
-//    }
-//    if (mFileName == NULL) {
-//        return -1;
-//    }
-//    *outStart = mStart;
-//    *outLength = mLength;
-//    return open(mFileName, O_RDONLY | O_BINARY);
-//}
-//const void* _FileAsset::ensureAlignment(FileMap* map)
-//{
-//    void* data = map->getDataPtr();
-//    if ((((size_t)data)&0x3) == 0) {
-//        // We can return this directly if it is aligned on a word
-//        // boundary.
-//        ALOGV("Returning aligned FileAsset %p (%s).", this,
-//                getAssetSource());
-//        return data;
-//    }
-//    // If not aligned on a word boundary, then we need to copy it into
-//    // our own buffer.
-//    ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this,
-//            getAssetSource(), (int)mLength);
-//    unsigned char* buf = new unsigned char[mLength];
-//    if (buf == NULL) {
-//        ALOGE("alloc of %ld bytes failed\n", (long) mLength);
-//        return NULL;
-//    }
-//    memcpy(buf, data, mLength);
-//    mBuf = buf;
-//    return buf;
-//}
-/*
- * ===========================================================================
- *      _CompressedAsset
- * ===========================================================================
- */
-private static class _CompressedAsset extends Asset {
-  private long     mStart;         // offset to start of compressed data
-  private long     mCompressedLen; // length of the compressed data
-  private long     mUncompressedLen; // length of the uncompressed data
-  private long     mOffset;        // current offset, 0 == start of uncomp data
-  //FileMap*    mMap;           // for memory-mapped input
-  private int         mFd;            // for file input
-  //StreamingZipInflater mZipInflater;  // for streaming large compressed assets
-  private byte[]  mBuf;       // for getBuffer()
-
-/*
- * Constructor.
- */
-_CompressedAsset(RandomAccessFile f) {
-  super(f);
-    mStart = 0;
-    mCompressedLen = 0;
-    mUncompressedLen = 0;
-    mOffset = 0;
-    // mMap = null;
-    mFd = -1;
-    //mZipInflater = null;
-    mBuf = null;
-
-    // Register the Asset with the global list here after it is fully constructed and its
-    // vtable pointer points to this concrete type. b/31113965
-    registerAsset(this);
-
-
-}
-/*
- * Destructor.  Release resources.
- */
-@Override
-public void finalize()
+  static Asset createFromCompressedFile(String fileName,
+      AccessMode mode)
   {
-    // close();
-    // Unregister the Asset from the global list here before it is destructed and while its vtable
-    // pointer still points to this concrete type. b/31113965
-    unregisterAsset(this);
+    return null;
+  //    _CompressedAsset pAsset;
+  //    status_t result;
+  //    off64_t fileLen;
+  //    bool scanResult;
+  //    long offset;
+  //    int method;
+  //    long uncompressedLen, compressedLen;
+  //    int fd;
+  //    fd = open(fileName, O_RDONLY | O_BINARY);
+  //    if (fd < 0)
+  //        return NULL;
+  //    fileLen = lseek(fd, 0, SEEK_END);
+  //    if (fileLen < 0) {
+  //        ::close(fd);
+  //        return NULL;
+  //    }
+  //    (void) lseek(fd, 0, SEEK_SET);
+  //    /* want buffered I/O for the file scan; must dup so fclose() is safe */
+  //    FILE* fp = fdopen(dup(fd), "rb");
+  //    if (fp == NULL) {
+  //        ::close(fd);
+  //        return NULL;
+  //    }
+  //    unsigned long crc32;
+  //    scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
+  //                    &compressedLen, &crc32);
+  //    offset = ftell(fp);
+  //    fclose(fp);
+  //    if (!scanResult) {
+  //        ALOGD("File '%s' is not in gzip format\n", fileName);
+  //        ::close(fd);
+  //        return NULL;
+  //    }
+  //    pAsset = new _CompressedAsset;
+  //    result = pAsset->openChunk(fd, offset, method, uncompressedLen,
+  //                compressedLen);
+  //    if (result != NO_ERROR) {
+  //        delete pAsset;
+  //        return NULL;
+  //    }
+  //    pAsset->mAccessMode = mode;
+  //    return pAsset;
+  }
+
+  public static Asset createFromZipEntry(ZipFileRO pZipFile, ZipEntryRO entry, String8 string8) {
+    return new CompressedAsset(pZipFile, entry, string8);
+  }
+
+  public abstract int getLength();
+
+  public abstract long size();
+
+  public abstract int read();
+
+  public abstract int read(byte[] b, int off, int len);
+
+  public abstract long seek(long offset, int whence);
+
+  public abstract void close();
+
+  /* set the asset source string */
+  void setAssetSource(String8 path) {
+    mAssetSource = path;
+  }
+
+  protected void registerAsset(Asset asset)
+{
+  int gCount = 0;
+    synchronized (gAssetLock) {
+      gAssets.add(asset);
+      gCount = gAssets.size();
+    }
+  if (kIsDebug) {
+    ALOGI("Creating Asset %p #%d\n", asset, gCount);
+  }
+
 }
-///*
-// * Open a chunk of compressed data inside a file.
-// *
-// * This currently just sets up some values and returns.  On the first
-// * read, we expand the entire file into a buffer and return data from it.
-// */
-//status_t _CompressedAsset::openChunk(int fd, off64_t offset,
-//    int compressionMethod, size_t uncompressedLen, size_t compressedLen)
-//{
-//    assert(mFd < 0);        // no re-open
-//    assert(mMap == NULL);
-//    assert(fd >= 0);
-//    assert(offset >= 0);
-//    assert(compressedLen > 0);
-//    if (compressionMethod != ZipFileRO::kCompressDeflated) {
-//        assert(false);
-//        return UNKNOWN_ERROR;
-//    }
-//    mStart = offset;
-//    mCompressedLen = compressedLen;
-//    mUncompressedLen = uncompressedLen;
-//    assert(mOffset == 0);
-//    mFd = fd;
-//    assert(mBuf == NULL);
-//    if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
-//        mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
-//    }
-//    return NO_ERROR;
-//}
-///*
-// * Open a chunk of compressed data in a mapped region.
-// *
-// * Nothing is expanded until the first read call.
-// */
-//status_t _CompressedAsset::openChunk(FileMap* dataMap, size_t uncompressedLen)
-//{
-//    assert(mFd < 0);        // no re-open
-//    assert(mMap == NULL);
-//    assert(dataMap != NULL);
-//    mMap = dataMap;
-//    mStart = -1;        // not used
-//    mCompressedLen = dataMap->getDataLength();
-//    mUncompressedLen = uncompressedLen;
-//    assert(mOffset == 0);
-//    if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
-//        mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
-//    }
-//    return NO_ERROR;
-//}
-///*
-// * Read data from a chunk of compressed data.
-// *
-// * [For now, that's just copying data out of a buffer.]
-// */
-//ssize_t _CompressedAsset::read(void* buf, size_t count)
-//{
-//    size_t maxLen;
-//    size_t actual;
-//    assert(mOffset >= 0 && mOffset <= mUncompressedLen);
-//    /* If we're relying on a streaming inflater, go through that */
-//    if (mZipInflater) {
-//        actual = mZipInflater->read(buf, count);
-//    } else {
-//        if (mBuf == NULL) {
-//            if (getBuffer(false) == NULL)
-//                return -1;
-//        }
-//        assert(mBuf != NULL);
-//        /* adjust count if we're near EOF */
-//        maxLen = mUncompressedLen - mOffset;
-//        if (count > maxLen)
-//            count = maxLen;
-//        if (!count)
-//            return 0;
-//        /* copy from buffer */
-//        //printf("comp buf read\n");
-//        memcpy(buf, (char*)mBuf + mOffset, count);
-//        actual = count;
-//    }
-//    mOffset += actual;
-//    return actual;
-//}
-///*
-// * Handle a seek request.
-// *
-// * If we're working in a streaming mode, this is going to be fairly
-// * expensive, because it requires plowing through a bunch of compressed
-// * data.
-// */
-//off64_t _CompressedAsset::seek(off64_t offset, int whence)
-//{
-//    off64_t newPosn;
-//    // compute new position within chunk
-//    newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
-//    if (newPosn == (off64_t) -1)
-//        return newPosn;
-//    if (mZipInflater) {
-//        mZipInflater->seekAbsolute(newPosn);
-//    }
-//    mOffset = newPosn;
-//    return mOffset;
-//}
-///*
-// * Close the asset.
-// */
-//void _CompressedAsset::close(void)
-//{
-//    if (mMap != NULL) {
-//        delete mMap;
-//        mMap = NULL;
-//    }
-//    delete[] mBuf;
-//    mBuf = NULL;
-//    delete mZipInflater;
-//    mZipInflater = NULL;
-//    if (mFd > 0) {
-//        ::close(mFd);
-//        mFd = -1;
-//    }
-//}
-///*
-// * Get a pointer to a read-only buffer of data.
-// *
-// * The first time this is called, we expand the compressed data into a
-// * buffer.
-// */
-//const void* _CompressedAsset::getBuffer(bool)
-//{
-//    unsigned char* buf = NULL;
-//    if (mBuf != NULL)
-//        return mBuf;
-//    /*
-//     * Allocate a buffer and read the file into it.
-//     */
-//    buf = new unsigned char[mUncompressedLen];
-//    if (buf == NULL) {
-//        ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
-//        goto bail;
-//    }
-//    if (mMap != NULL) {
-//        if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf,
-//                mUncompressedLen, mCompressedLen))
-//            goto bail;
-//    } else {
-//        assert(mFd >= 0);
-//        /*
-//         * Seek to the start of the compressed data.
-//         */
-//        if (lseek(mFd, mStart, SEEK_SET) != mStart)
-//            goto bail;
-//        /*
-//         * Expand the data into it.
-//         */
-//        if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
-//                mCompressedLen))
-//            goto bail;
-//    }
-//    /*
-//     * Success - now that we have the full asset in RAM we
-//     * no longer need the streaming inflater
-//     */
-//    delete mZipInflater;
-//    mZipInflater = NULL;
-//    mBuf = buf;
-//    buf = NULL;
-//bail:
-//    delete[] buf;
-//    return mBuf;
+
+  protected void unregisterAsset(Asset asset) {
+  int gCount = 0;
+  synchronized (gAssetLock) {
+    gAssets.remove(asset);
+    gCount = gAssets.size();
+  }
+  if (kIsDebug) {
+        ALOGI("Destroying Asset in %p #%d\n", asset, gCount);
+    }
+}
+
+  protected int getGlobalCount()
+{
+  synchronized (gAssetLock) {
+    return gAssets.size();
+  }
+}
+
+  /*
+ * Return a read-only pointer to a buffer.
+ *
+ * We can either read the whole thing in or map the relevant piece of
+ * the source file.  Ideally a map would be established at a higher
+ * level and we'd be using a different object, but we didn't, so we
+ * deal with it here.
+ */
+  public byte[] getBuffer(boolean wordAligned) {
+    int size = (int) size();
+    byte[] bytes = new byte[size];
+    if (read(bytes, 0, size) != size) {
+      throw new RuntimeException("failed to read " + size);
+    }
+    return bytes;
+  }
+
+  //      public:
+//      virtual ~Asset(void) = default;
+//      static int32_t getGlobalCount();
+//      static String8 getAssetAllocations();
 //
-}
+//    /* used when opening an asset */
+  public enum AccessMode {
+    ACCESS_UNKNOWN,
+    /* read chunks, and seek forward and backward */
+    ACCESS_RANDOM,
+    /* read sequentially, with an occasional forward seek */
+    ACCESS_STREAMING,
+    /* caller plans to ask for a read-only buffer with all data */
+    ACCESS_BUFFER,
+  }
+
+  //const void* _FileAsset::getBuffer(bool wordAligned)
+  //{
+  //    /* subsequent requests just use what we did previously */
+  //    if (mBuf != NULL)
+  //        return mBuf;
+  //    if (mMap != NULL) {
+  //        if (!wordAligned) {
+  //            return  mMap->getDataPtr();
+  //        }
+  //        return ensureAlignment(mMap);
+  //    }
+  //    assert(mFp != NULL);
+  //    if (mLength < kReadVsMapThreshold) {
+  //        unsigned char* buf;
+  //        long allocLen;
+  //        /* zero-length files are allowed; not sure about zero-len allocs */
+  //        /* (works fine with gcc + x86linux) */
+  //        allocLen = mLength;
+  //        if (mLength == 0)
+  //            allocLen = 1;
+  //        buf = new unsigned char[allocLen];
+  //        if (buf == NULL) {
+  //            ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
+  //            return NULL;
+  //        }
+  //        ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
+  //        if (mLength > 0) {
+  //            long oldPosn = ftell(mFp);
+  //            fseek(mFp, mStart, SEEK_SET);
+  //            if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
+  //                ALOGE("failed reading %ld bytes\n", (long) mLength);
+  //                delete[] buf;
+  //                return NULL;
+  //            }
+  //            fseek(mFp, oldPosn, SEEK_SET);
+  //        }
+  //        ALOGV(" getBuffer: loaded into buffer\n");
+  //        mBuf = buf;
+  //        return mBuf;
+  //    } else {
+  //        FileMap* map;
+  //        map = new FileMap;
+  //        if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
+  //            delete map;
+  //            return NULL;
+  //        }
+  //        ALOGV(" getBuffer: mapped\n");
+  //        mMap = map;
+  //        if (!wordAligned) {
+  //            return  mMap->getDataPtr();
+  //        }
+  //        return ensureAlignment(mMap);
+  //    }
+  //}
+  //int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
+  //{
+  //    if (mMap != NULL) {
+  //        const char* fname = mMap->getFileName();
+  //        if (fname == NULL) {
+  //            fname = mFileName;
+  //        }
+  //        if (fname == NULL) {
+  //            return -1;
+  //        }
+  //        *outStart = mMap->getDataOffset();
+  //        *outLength = mMap->getDataLength();
+  //        return open(fname, O_RDONLY | O_BINARY);
+  //    }
+  //    if (mFileName == NULL) {
+  //        return -1;
+  //    }
+  //    *outStart = mStart;
+  //    *outLength = mLength;
+  //    return open(mFileName, O_RDONLY | O_BINARY);
+  //}
+  //const void* _FileAsset::ensureAlignment(FileMap* map)
+  //{
+  //    void* data = map->getDataPtr();
+  //    if ((((size_t)data)&0x3) == 0) {
+  //        // We can return this directly if it is aligned on a word
+  //        // boundary.
+  //        ALOGV("Returning aligned FileAsset %p (%s).", this,
+  //                getAssetSource());
+  //        return data;
+  //    }
+  //    // If not aligned on a word boundary, then we need to copy it into
+  //    // our own buffer.
+  //    ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this,
+  //            getAssetSource(), (int)mLength);
+  //    unsigned char* buf = new unsigned char[mLength];
+  //    if (buf == NULL) {
+  //        ALOGE("alloc of %ld bytes failed\n", (long) mLength);
+  //        return NULL;
+  //    }
+  //    memcpy(buf, data, mLength);
+  //    mBuf = buf;
+  //    return buf;
+  //}
+  /*
+   * ===========================================================================
+   *      _CompressedAsset
+   * ===========================================================================
+   */
+  private static class CompressedAsset extends Asset {
+
+    private final ZipFileRO pZipFile;
+    private final ZipEntryRO entry;
+    private final InputStream entryInputStream;
+    private long     mStart;         // offset to start of compressed data
+    private long     mCompressedLen; // length of the compressed data
+    private long     mUncompressedLen; // length of the uncompressed data
+    private long     mOffset;        // current offset, 0 == start of uncomp data
+    //FileMap*    mMap;           // for memory-mapped input
+    private int         mFd;            // for file input
+    //StreamingZipInflater mZipInflater;  // for streaming large compressed assets
+    private byte[]  mBuf;       // for getBuffer()
+
+    /*
+     * Constructor.
+     */
+  public CompressedAsset(ZipFileRO pZipFile, ZipEntryRO entry, String8 string8) {
+    this.pZipFile = pZipFile;
+    this.entry = entry;
+    mStart = 0;
+      mCompressedLen = 0;
+      mUncompressedLen = 0;
+      mOffset = 0;
+      // mMap = null;
+      mFd = -1;
+      //mZipInflater = null;
+      mBuf = null;
+
+      // Register the Asset with the global list here after it is fully constructed and its
+      // vtable pointer points to this concrete type. b/31113965
+      registerAsset(this);
+
+    ZipEntry zipEntry = entry.entry;
+    ZipFile zipFile = pZipFile.mHandle.zipFile;
+    try {
+      entryInputStream = zipFile.getInputStream(zipEntry);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+    @Override
+    public int getLength() {
+      return (int) entry.entry.getSize();
+    }
+
+    @Override
+    public long size() {
+      return entry.entry.getSize();
+    }
+
+    @Override
+    public int read() {
+      try {
+        return entryInputStream.read();
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) {
+      try {
+        return entryInputStream.read(b, off, len);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    public long seek(long offset, int whence) {
+      throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public void close() {
+      try {
+        entryInputStream.close();
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    /*
+     * Destructor.  Release resources.
+     */
+  @Override
+  public void finalize()
+    {
+      // close();
+      // Unregister the Asset from the global list here before it is destructed and while its vtable
+      // pointer still points to this concrete type. b/31113965
+      unregisterAsset(this);
+  }
+  ///*
+  // * Open a chunk of compressed data inside a file.
+  // *
+  // * This currently just sets up some values and returns.  On the first
+  // * read, we expand the entire file into a buffer and return data from it.
+  // */
+  //status_t _CompressedAsset::openChunk(int fd, off64_t offset,
+  //    int compressionMethod, size_t uncompressedLen, size_t compressedLen)
+  //{
+  //    assert(mFd < 0);        // no re-open
+  //    assert(mMap == NULL);
+  //    assert(fd >= 0);
+  //    assert(offset >= 0);
+  //    assert(compressedLen > 0);
+  //    if (compressionMethod != ZipFileRO::kCompressDeflated) {
+  //        assert(false);
+  //        return UNKNOWN_ERROR;
+  //    }
+  //    mStart = offset;
+  //    mCompressedLen = compressedLen;
+  //    mUncompressedLen = uncompressedLen;
+  //    assert(mOffset == 0);
+  //    mFd = fd;
+  //    assert(mBuf == NULL);
+  //    if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
+  //        mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
+  //    }
+  //    return NO_ERROR;
+  //}
+  ///*
+  // * Open a chunk of compressed data in a mapped region.
+  // *
+  // * Nothing is expanded until the first read call.
+  // */
+  //status_t _CompressedAsset::openChunk(FileMap* dataMap, size_t uncompressedLen)
+  //{
+  //    assert(mFd < 0);        // no re-open
+  //    assert(mMap == NULL);
+  //    assert(dataMap != NULL);
+  //    mMap = dataMap;
+  //    mStart = -1;        // not used
+  //    mCompressedLen = dataMap->getDataLength();
+  //    mUncompressedLen = uncompressedLen;
+  //    assert(mOffset == 0);
+  //    if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
+  //        mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
+  //    }
+  //    return NO_ERROR;
+  //}
+  ///*
+  // * Read data from a chunk of compressed data.
+  // *
+  // * [For now, that's just copying data out of a buffer.]
+  // */
+  //ssize_t _CompressedAsset::read(void* buf, size_t count)
+  //{
+  //    size_t maxLen;
+  //    size_t actual;
+  //    assert(mOffset >= 0 && mOffset <= mUncompressedLen);
+  //    /* If we're relying on a streaming inflater, go through that */
+  //    if (mZipInflater) {
+  //        actual = mZipInflater->read(buf, count);
+  //    } else {
+  //        if (mBuf == NULL) {
+  //            if (getBuffer(false) == NULL)
+  //                return -1;
+  //        }
+  //        assert(mBuf != NULL);
+  //        /* adjust count if we're near EOF */
+  //        maxLen = mUncompressedLen - mOffset;
+  //        if (count > maxLen)
+  //            count = maxLen;
+  //        if (!count)
+  //            return 0;
+  //        /* copy from buffer */
+  //        //printf("comp buf read\n");
+  //        memcpy(buf, (char*)mBuf + mOffset, count);
+  //        actual = count;
+  //    }
+  //    mOffset += actual;
+  //    return actual;
+  //}
+  ///*
+  // * Handle a seek request.
+  // *
+  // * If we're working in a streaming mode, this is going to be fairly
+  // * expensive, because it requires plowing through a bunch of compressed
+  // * data.
+  // */
+  //off64_t _CompressedAsset::seek(off64_t offset, int whence)
+  //{
+  //    off64_t newPosn;
+  //    // compute new position within chunk
+  //    newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
+  //    if (newPosn == (off64_t) -1)
+  //        return newPosn;
+  //    if (mZipInflater) {
+  //        mZipInflater->seekAbsolute(newPosn);
+  //    }
+  //    mOffset = newPosn;
+  //    return mOffset;
+  //}
+  ///*
+  // * Close the asset.
+  // */
+  //void _CompressedAsset::close(void)
+  //{
+  //    if (mMap != NULL) {
+  //        delete mMap;
+  //        mMap = NULL;
+  //    }
+  //    delete[] mBuf;
+  //    mBuf = NULL;
+  //    delete mZipInflater;
+  //    mZipInflater = NULL;
+  //    if (mFd > 0) {
+  //        ::close(mFd);
+  //        mFd = -1;
+  //    }
+  //}
+  ///*
+  // * Get a pointer to a read-only buffer of data.
+  // *
+  // * The first time this is called, we expand the compressed data into a
+  // * buffer.
+  // */
+  //const void* _CompressedAsset::getBuffer(bool)
+  //{
+  //    unsigned char* buf = NULL;
+  //    if (mBuf != NULL)
+  //        return mBuf;
+  //    /*
+  //     * Allocate a buffer and read the file into it.
+  //     */
+  //    buf = new unsigned char[mUncompressedLen];
+  //    if (buf == NULL) {
+  //        ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
+  //        goto bail;
+  //    }
+  //    if (mMap != NULL) {
+  //        if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf,
+  //                mUncompressedLen, mCompressedLen))
+  //            goto bail;
+  //    } else {
+  //        assert(mFd >= 0);
+  //        /*
+  //         * Seek to the start of the compressed data.
+  //         */
+  //        if (lseek(mFd, mStart, SEEK_SET) != mStart)
+  //            goto bail;
+  //        /*
+  //         * Expand the data into it.
+  //         */
+  //        if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
+  //                mCompressedLen))
+  //            goto bail;
+  //    }
+  //    /*
+  //     * Success - now that we have the full asset in RAM we
+  //     * no longer need the streaming inflater
+  //     */
+  //    delete mZipInflater;
+  //    mZipInflater = NULL;
+  //    mBuf = buf;
+  //    buf = NULL;
+  //bail:
+  //    delete[] buf;
+  //    return mBuf;
+  //
+  }
 }
diff --git a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java
index c35a66d..609497e 100644
--- a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java
+++ b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java
@@ -1,5 +1,7 @@
 package org.robolectric.res.android;
 
+import static org.robolectric.res.android.Util.ALOGD;
+import static org.robolectric.res.android.Util.ALOGI;
 import static org.robolectric.res.android.Util.ALOGV;
 import static org.robolectric.res.android.Util.ALOGW;
 import static org.robolectric.res.android.Util.ATRACE_CALL;
@@ -9,20 +11,24 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.ref.WeakReference;
 import java.net.URL;
-import java.nio.file.Path;
+import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 import javax.annotation.Nullable;
 import org.robolectric.res.android.Asset.AccessMode;
-import org.robolectric.res.android.CppAssetManager.FileType;
+import org.robolectric.res.android.ZipFileRO.ZipEntryRO;
 
 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/libs/androidfw/AssetManager.cpp
 public class CppAssetManager {
+  private static final boolean kIsDebug = false;
 
   enum FileType {
     kFileTypeUnknown,
@@ -67,8 +73,7 @@
 
   private final Object mLock = new Object();
 
-  //ZipSet          mZipSet;
-  Object mZipSet;
+  ZipSet          mZipSet = new ZipSet();
 
   private final List<asset_path> mAssetPaths = new ArrayList<>();
   private String mLocale;
@@ -88,7 +93,7 @@
 //  
 
   // static Asset final kExcludedAsset = (Asset*) 0xd000000d;
-  static final Asset kExcludedAsset = new Asset(null);
+  static final Asset kExcludedAsset = new FileAsset(null);
 //  
 //  static volatile int gCount = 0;
 //  
@@ -537,7 +542,7 @@
   }
 
   boolean appendPathToResTable(final asset_path ap, boolean appAsLib) {
-    URL resource = getClass().getResource("/resources.ap_");
+    URL resource = getClass().getResource("/resources.ap_"); // todo get this from asset_path
     System.out.println("Reading ARSC file  from " + resource);
     LOG_FATAL_IF(resource == null, "Could not find resources.ap_");
     try {
@@ -820,48 +825,46 @@
 
       /* look inside the zip file */
       } else {
-        throw new UnsupportedOperationException("not implemented yet");
-//          String8 path = new String8(fileName);
-//
-//          /* check the appropriate Zip file */
-//          ZipFileRO* pZip = getZipFileLocked(ap);
-//          if (pZip != null) {
-//              //printf("GOT zip, checking NA '%s'\n", (final char*) path);
-//              ZipEntryRO entry = pZip.findEntryByName(path.string());
-//              if (entry != null) {
-//                  //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
-//                  pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
-//                  pZip.releaseEntry(entry);
-//              }
-//          }
-//
-//          if (pAsset != null) {
-//              /* create a "source" name, for debug/display */
-//              pAsset.setAssetSource(
-//                      createZipSourceNameLocked(ZipSet.getPathName(ap.path.string()), String8(""),
-//                                                  String8(fileName)));
-//          }
+          String8 path = new String8(fileName);
+
+          /* check the appropriate Zip file */
+          ZipFileRO pZip = getZipFileLocked(ap);
+          if (pZip != null) {
+              //printf("GOT zip, checking NA '%s'\n", (final char*) path);
+              ZipEntryRO entry = pZip.findEntryByName(path.string());
+              if (entry != null) {
+                  //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
+                pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
+                  pZip.releaseEntry(entry);
+              }
+          }
+
+          if (pAsset != null) {
+              /* create a "source" name, for debug/display */
+              pAsset.setAssetSource(
+                      createZipSourceNameLocked(ap.path.string(), "", fileName));
+          }
       }
 
-      return pAsset;
+    return pAsset;
   }
-//  
-//  /*
-//   * Create a "source name" for a file from a Zip archive.
-//   */
-//  String8 createZipSourceNameLocked(final String8& zipFileName,
-//      final String8& dirName, final String8& fileName)
-//  {
-//      String8 sourceName("zip:");
-//      sourceName.append(zipFileName);
-//      sourceName.append(":");
-//      if (dirName.length() > 0) {
-//          sourceName.appendPath(dirName);
-//      }
-//      sourceName.appendPath(fileName);
-//      return sourceName;
-//  }
-//  
+
+  /*
+   * Create a "source name" for a file from a Zip archive.
+   */
+  String8 createZipSourceNameLocked(final String zipFileName,
+      final String dirName, final String fileName)
+  {
+      String8 sourceName = new String8("zip:");
+      sourceName.append(zipFileName);
+      sourceName.append(":");
+      if (dirName.length() > 0) {
+          sourceName.appendPath(dirName);
+      }
+      sourceName.appendPath(fileName);
+      return sourceName;
+  }
+
 //  /*
 //   * Create a path to a loose asset (asset-base/app/rootDir).
 //   */
@@ -872,17 +875,17 @@
 //      return path;
 //  }
 //  
-//  /*
-//   * Return a pointer to one of our open Zip archives.  Returns null if no
-//   * matching Zip file exists.
-//   */
-//  ZipFileRO* getZipFileLocked(final asset_path& ap)
-//  {
-//      ALOGV("getZipFileLocked() in %p\n", this);
-//  
-//      return mZipSet.getZip(ap.path);
-//  }
-//  
+  /*
+   * Return a pointer to one of our open Zip archives.  Returns null if no
+   * matching Zip file exists.
+   */
+  ZipFileRO getZipFileLocked(final asset_path ap)
+  {
+      ALOGV("getZipFileLocked() in %s\n", this);
+
+      return mZipSet.getZip(ap.path.string());
+  }
+
   /*
    * Try to open an asset from a file on disk.
    *
@@ -910,37 +913,38 @@
 
       return pAsset;
   }
-//  
-//  /*
-//   * Given an entry in a Zip archive, create a new Asset object.
-//   *
-//   * If the entry is uncompressed, we may want to create or share a
-//   * slice of shared memory.
-//   */
-//  Asset* openAssetFromZipLocked(final ZipFileRO* pZipFile,
-//      final ZipEntryRO entry, AccessMode mode, final String8& entryName)
-//  {
-//      Asset* pAsset = null;
-//  
-//      // TODO: look for previously-created shared memory slice?
-//      uint16_t method;
-//      uint32_t uncompressedLen;
-//  
-//      //printf("USING Zip '%s'\n", pEntry.getFileName());
-//  
-//      if (!pZipFile.getEntryInfo(entry, &method, &uncompressedLen, null, null,
-//              null, null))
-//      {
-//          ALOGW("getEntryInfo failed\n");
-//          return null;
-//      }
-//  
-//      FileMap* dataMap = pZipFile.createEntryFileMap(entry);
+
+  /*
+   * Given an entry in a Zip archive, create a new Asset object.
+   *
+   * If the entry is uncompressed, we may want to create or share a
+   * slice of shared memory.
+   */
+  Asset openAssetFromZipLocked(final ZipFileRO pZipFile,
+      final ZipEntryRO entry, AccessMode mode, final String8 entryName)
+  {
+      Asset pAsset = null;
+
+      // TODO: look for previously-created shared memory slice?
+      Ref<Short> method = new Ref<>((short) 0);
+      Ref<Long> uncompressedLen = new Ref<>(0L);
+
+      //printf("USING Zip '%s'\n", pEntry.getFileName());
+
+      if (!pZipFile.getEntryInfo(entry, method, uncompressedLen, null, null,
+              null, null))
+      {
+          ALOGW("getEntryInfo failed\n");
+          return null;
+      }
+
+      return Asset.createFromZipEntry(pZipFile, entry, entryName);
+//      FileMap dataMap = pZipFile.createEntryFileMap(entry);
 //      if (dataMap == null) {
 //          ALOGW("create map from entry failed\n");
 //          return null;
 //      }
-//  
+//
 //      if (method == ZipFileRO.kCompressStored) {
 //          pAsset = Asset.createFromUncompressedMap(dataMap, mode);
 //          ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(),
@@ -955,10 +959,10 @@
 //          /* unexpected */
 //          ALOGW("create from segment failed\n");
 //      }
-//  
+
 //      return pAsset;
-//  }
-//  
+  }
+
 //  /*
 //   * Open a directory in the asset namespace.
 //   *
@@ -1412,61 +1416,78 @@
 //      }
 //  #endif
 //  }
-//  
-//  /*
-//   * ===========================================================================
-//   *      SharedZip
-//   * ===========================================================================
-//   */
-//  
-//  
-//  Mutex SharedZip.gLock;
-//  DefaultKeyedVector<String8, wp<SharedZip> > SharedZip.gOpen;
-//  
-//  SharedZip.SharedZip(final String8& path, time_t modWhen)
-//      : mPath(path), mZipFile(null), mModWhen(modWhen),
-//        mResourceTableAsset(null), mResourceTable(null)
-//  {
-//      if (kIsDebug) {
-//          ALOGI("Creating SharedZip %p %s\n", this, (final char*)mPath);
-//      }
-//      ALOGV("+++ opening zip '%s'\n", mPath.string());
-//      mZipFile = ZipFileRO.open(mPath.string());
-//      if (mZipFile == null) {
-//          ALOGD("failed to open Zip archive '%s'\n", mPath.string());
-//      }
-//  }
-//  
-//  sp<SharedZip> SharedZip.get(final String8& path,
-//          boolean createIfNotPresent)
-//  {
-//      AutoMutex _l(gLock);
-//      time_t modWhen = getFileModDate(path);
-//      sp<SharedZip> zip = gOpen.valueFor(path).promote();
-//      if (zip != null && zip.mModWhen == modWhen) {
-//          return zip;
-//      }
-//      if (zip == null && !createIfNotPresent) {
-//          return null;
-//      }
-//      zip = new SharedZip(path, modWhen);
-//      gOpen.add(path, zip);
-//      return zip;
-//  
-//  }
-//  
-//  ZipFileRO* SharedZip.getZip()
-//  {
-//      return mZipFile;
-//  }
-//  
+
+  /*
+   * ===========================================================================
+   *      SharedZip
+   * ===========================================================================
+   */
+
+  static class SharedZip /*: public RefBase */{
+    String mPath;
+    ZipFileRO mZipFile;
+    long mModWhen;
+
+    Asset mResourceTableAsset;
+    ResTable mResourceTable;
+
+    List<asset_path> mOverlays;
+
+    final static Object gLock = new Object();
+    final static Map<String8, WeakReference<SharedZip>> gOpen = new HashMap<>();
+
+    public SharedZip(String path, long modWhen) {
+      this.mPath = path;
+      this.mZipFile = null;
+      this.mModWhen = modWhen;
+      this.mResourceTableAsset = null;
+      this.mResourceTable = null;
+
+      if (kIsDebug) {
+          ALOGI("Creating SharedZip %s %s\n", this, mPath);
+      }
+      ALOGV("+++ opening zip '%s'\n", mPath);
+      mZipFile = ZipFileRO.open(mPath);
+      if (mZipFile == null) {
+          ALOGD("failed to open Zip archive '%s'\n", mPath);
+      }
+  }
+
+    static SharedZip get(final String8 path) {
+      return get(path, true);
+    }
+
+    static SharedZip get(final String8 path, boolean createIfNotPresent) {
+      synchronized (gLock) {
+        long modWhen = getFileModDate(path.string());
+        WeakReference<SharedZip> ref = gOpen.get(path);
+        SharedZip zip = ref == null ? null : ref.get();
+        if (zip != null && zip.mModWhen == modWhen) {
+          return zip;
+        }
+        if (zip == null && !createIfNotPresent) {
+          return null;
+        }
+        zip = new SharedZip(path.string(), modWhen);
+        gOpen.put(path, new WeakReference<>(zip));
+        return zip;
+
+      }
+
+  }
+
+    ZipFileRO getZip()
+  {
+    return mZipFile;
+  }
+
 //  Asset* SharedZip.getResourceTableAsset()
 //  {
 //      AutoMutex _l(gLock);
 //      ALOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
 //      return mResourceTableAsset;
 //  }
-//  
+//
 //  Asset* SharedZip.setResourceTableAsset(Asset* asset)
 //  {
 //      {
@@ -1482,13 +1503,13 @@
 //      delete asset;
 //      return mResourceTableAsset;
 //  }
-//  
+//
 //  ResTable* SharedZip.getResourceTable()
 //  {
 //      ALOGV("Getting from SharedZip %p resource table %p\n", this, mResourceTable);
 //      return mResourceTable;
 //  }
-//  
+//
 //  ResTable* SharedZip.setResourceTable(ResTable* res)
 //  {
 //      {
@@ -1501,18 +1522,18 @@
 //      delete res;
 //      return mResourceTable;
 //  }
-//  
+//
 //  boolean SharedZip.isUpToDate()
 //  {
 //      time_t modWhen = getFileModDate(mPath.string());
 //      return mModWhen == modWhen;
 //  }
-//  
+//
 //  void SharedZip.addOverlay(final asset_path& ap)
 //  {
 //      mOverlays.add(ap);
 //  }
-//  
+//
 //  boolean SharedZip.getOverlay(int idx, asset_path* out) final
 //  {
 //      if (idx >= mOverlays.size()) {
@@ -1521,7 +1542,7 @@
 //      *out = mOverlays[idx];
 //      return true;
 //  }
-//  
+//
 //  SharedZip.~SharedZip()
 //  {
 //      if (kIsDebug) {
@@ -1538,46 +1559,62 @@
 //          ALOGV("Closed '%s'\n", mPath.string());
 //      }
 //  }
-//  
-//  /*
-//   * ===========================================================================
-//   *      ZipSet
-//   * ===========================================================================
-//   */
-//  
-//  /*
-//   * Destructor.  Close any open archives.
-//   */
+//
+};
+
+
+  /*
+ * Manage a set of Zip files.  For each file we need a pointer to the
+ * ZipFile and a time_t with the file's modification date.
+ *
+ * We currently only have two zip files (current app, "common" app).
+ * (This was originally written for 8, based on app/locale/vendor.)
+ */
+  static class ZipSet {
+
+    List<String> mZipPath = new ArrayList<>();
+    List<SharedZip> mZipFile = new ArrayList<>();
+
+  /*
+   * ===========================================================================
+   *      ZipSet
+   * ===========================================================================
+   */
+
+  /*
+   * Destructor.  Close any open archives.
+   */
 //  ZipSet.~ZipSet(void)
-//  {
-//      int N = mZipFile.size();
-//      for (int i = 0; i < N; i++)
-//          closeZip(i);
-//  }
-//  
-//  /*
-//   * Close a Zip file and reset the entry.
-//   */
-//  void ZipSet.closeZip(int idx)
-//  {
-//      mZipFile.editItemAt(idx) = null;
-//  }
-//  
-//  
-//  /*
-//   * Retrieve the appropriate Zip file from the set.
-//   */
-//  ZipFileRO* ZipSet.getZip(final String8& path)
-//  {
-//      int idx = getIndex(path);
-//      sp<SharedZip> zip = mZipFile[idx];
-//      if (zip == null) {
-//          zip = SharedZip.get(path);
-//          mZipFile.editItemAt(idx) = zip;
-//      }
-//      return zip.getZip();
-//  }
-//  
+    protected void finalize()
+  {
+      int N = mZipFile.size();
+      for (int i = 0; i < N; i++)
+          closeZip(i);
+  }
+
+  /*
+   * Close a Zip file and reset the entry.
+   */
+  void closeZip(int idx)
+  {
+      mZipFile.set(idx, null);
+  }
+
+
+  /*
+   * Retrieve the appropriate Zip file from the set.
+   */
+  ZipFileRO getZip(final String path)
+  {
+      int idx = getIndex(path);
+      SharedZip zip = mZipFile.get(idx);
+      if (zip == null) {
+          zip = SharedZip.get(new String8(path));
+          mZipFile.set(idx, zip);
+      }
+      return zip.getZip();
+  }
+
 //  Asset* ZipSet.getZipResourceTableAsset(final String8& path)
 //  {
 //      int idx = getIndex(path);
@@ -1588,7 +1625,7 @@
 //      }
 //      return zip.getResourceTableAsset();
 //  }
-//  
+//
 //  Asset* ZipSet.setZipResourceTableAsset(final String8& path,
 //                                                   Asset* asset)
 //  {
@@ -1597,7 +1634,7 @@
 //      // doesn't make sense to call before previously accessing.
 //      return zip.setResourceTableAsset(asset);
 //  }
-//  
+//
 //  ResTable* ZipSet.getZipResourceTable(final String8& path)
 //  {
 //      int idx = getIndex(path);
@@ -1608,7 +1645,7 @@
 //      }
 //      return zip.getResourceTable();
 //  }
-//  
+//
 //  ResTable* ZipSet.setZipResourceTable(final String8& path,
 //                                                      ResTable* res)
 //  {
@@ -1617,7 +1654,7 @@
 //      // doesn't make sense to call before previously accessing.
 //      return zip.setResourceTable(res);
 //  }
-//  
+//
 //  /*
 //   * Generate the partial pathname for the specified archive.  The caller
 //   * gets to prepend the asset root directory.
@@ -1628,7 +1665,7 @@
 //  {
 //      return String8(zipPath);
 //  }
-//  
+//
 //  boolean ZipSet.isUpToDate()
 //  {
 //      final int N = mZipFile.size();
@@ -1639,14 +1676,14 @@
 //      }
 //      return true;
 //  }
-//  
+//
 //  void ZipSet.addOverlay(final String8& path, final asset_path& overlay)
 //  {
 //      int idx = getIndex(path);
 //      sp<SharedZip> zip = mZipFile[idx];
 //      zip.addOverlay(overlay);
 //  }
-//  
+//
 //  boolean ZipSet.getOverlay(final String8& path, int idx, asset_path* out) final
 //  {
 //      sp<SharedZip> zip = SharedZip.get(path, false);
@@ -1655,26 +1692,34 @@
 //      }
 //      return zip.getOverlay(idx, out);
 //  }
-//  
-//  /*
-//   * Compute the zip file's index.
-//   *
-//   * "appName", "locale", and "vendor" should be set to null to indicate the
-//   * default directory.
-//   */
-//  int ZipSet.getIndex(final String8& zip) final
-//  {
-//      final int N = mZipPath.size();
-//      for (int i=0; i<N; i++) {
-//          if (mZipPath[i] == zip) {
-//              return i;
-//          }
-//      }
-//  
-//      mZipPath.add(zip);
-//      mZipFile.add(null);
-//  
-//      return mZipPath.size()-1;
-//  }
-                                                                                  
+//
+  /*
+   * Compute the zip file's index.
+   *
+   * "appName", "locale", and "vendor" should be set to null to indicate the
+   * default directory.
+   */
+  int getIndex(final String zip)
+  {
+      final int N = mZipPath.size();
+      for (int i=0; i<N; i++) {
+          if (Objects.equals(mZipPath.get(i), zip)) {
+              return i;
+          }
+      }
+
+      mZipPath.add(zip);
+      mZipFile.add(null);
+
+      return mZipPath.size()-1;
+  }
+  }
+
+  private static long getFileModDate(String path) {
+    try {
+      return Files.getLastModifiedTime(Paths.get(path)).toMillis();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
 }
diff --git a/resources/src/main/java/org/robolectric/res/android/FileAsset.java b/resources/src/main/java/org/robolectric/res/android/FileAsset.java
new file mode 100644
index 0000000..1d8540e
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/FileAsset.java
@@ -0,0 +1,369 @@
+package org.robolectric.res.android;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Class providing access to a read-only asset.  Asset objects are NOT thread-safe, and should not
+ * be shared across threads.
+ *
+ * transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/include/androidfw/Asset.h
+ * and https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/libs/androidfw/Asset.cpp
+ *
+ * Instances of this class provide read-only operations on a byte stream.
+ * Access may
+ * be optimized for streaming, random, or whole buffer modes.  All operations are supported
+ * regardless of how the file was opened, but some things will be less efficient.  [pass that
+ * in??]. "Asset" is the base class for all types of assets.  The classes below
+ * provide most of the implementation.  The AssetManager uses one of the static "create"
+ * functions defined here to create a new instance.
+ */
+public class FileAsset extends Asset {
+  protected final RandomAccessFile f;
+
+  public FileAsset(RandomAccessFile f) {
+    this.f = f;
+  }
+
+  @Override
+  public int getLength() {
+    return (int) size();
+  }
+
+  @Override
+  public long size() {
+    try {
+      return f.length();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public int read() {
+    try {
+      return f.read();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public int read(byte[] b, int off, int len) {
+    try {
+      return f.read(b, off, len);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public long seek(long offset, int whence) {
+    throw new UnsupportedOperationException("not yet implemented");
+//    if (whence > 0) {
+//      SEEK_END
+//    } else if (whence < 0) {
+//      SEEK_SET
+//    } else {
+//      SEEK_CUR
+//    }
+//    return f.seek();
+  }
+
+  @Override
+  public void close() {
+    if (onClose != null) {
+      onClose.run();
+    }
+    try {
+      f.close();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+  //
+
+  /*
+ * Create a new Asset from a compressed file on disk.  There is a fair chance
+ * that the file doesn't actually exist.
+ *
+ * We currently support gzip files.  We might want to handle .bz2 someday.
+ */
+
+  //#if 0
+///*
+// * Create a new Asset from part of an open file.
+// */
+///*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
+//    size_t length, AccessMode mode)
+//{
+//    _FileAsset* pAsset;
+//    status_t result;
+//    pAsset = new _FileAsset;
+//    result = pAsset->openChunk(NULL, fd, offset, length);
+//    if (result != NO_ERROR)
+//        return NULL;
+//    pAsset->mAccessMode = mode;
+//    return pAsset;
+//}
+///*
+// * Create a new Asset from compressed data in an open file.
+// */
+///*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
+//    int compressionMethod, size_t uncompressedLen, size_t compressedLen,
+//    AccessMode mode)
+//{
+//    _CompressedAsset* pAsset;
+//    status_t result;
+//    pAsset = new _CompressedAsset;
+//    result = pAsset->openChunk(fd, offset, compressionMethod,
+//                uncompressedLen, compressedLen);
+//    if (result != NO_ERROR)
+//        return NULL;
+//    pAsset->mAccessMode = mode;
+//    return pAsset;
+//}
+//#endif
+///*
+// * Create a new Asset from a memory mapping.
+// */
+///*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
+//    AccessMode mode)
+//{
+//    _FileAsset* pAsset;
+//    status_t result;
+//    pAsset = new _FileAsset;
+//    result = pAsset->openChunk(dataMap);
+//    if (result != NO_ERROR)
+//        return NULL;
+//    pAsset->mAccessMode = mode;
+//    return pAsset;
+//}
+///*
+// * Create a new Asset from compressed data in a memory mapping.
+// */
+///*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
+//    size_t uncompressedLen, AccessMode mode)
+//{
+//    _CompressedAsset* pAsset;
+//    status_t result;
+//    pAsset = new _CompressedAsset;
+//    result = pAsset->openChunk(dataMap, uncompressedLen);
+//    if (result != NO_ERROR)
+//        return NULL;
+//    pAsset->mAccessMode = mode;
+//    return pAsset;
+//}
+///*
+// * Do generic seek() housekeeping.  Pass in the offset/whence values from
+// * the seek request, along with the current chunk offset and the chunk
+// * length.
+// *
+// * Returns the new chunk offset, or -1 if the seek is illegal.
+// */
+//long handleSeek(long offset, int whence, long curPosn, long maxPosn)
+//{
+//    long newOffset;
+//    switch (whence) {
+//    case SEEK_SET:
+//        newOffset = offset;
+//        break;
+//    case SEEK_CUR:
+//        newOffset = curPosn + offset;
+//        break;
+//    case SEEK_END:
+//        newOffset = maxPosn + offset;
+//        break;
+//    default:
+//        ALOGW("unexpected whence %d\n", whence);
+//        // this was happening due to an off64_t size mismatch
+//        assert(false);
+//        return (off64_t) -1;
+//    }
+//    if (newOffset < 0 || newOffset > maxPosn) {
+//        ALOGW("seek out of range: want %ld, end=%ld\n",
+//            (long) newOffset, (long) maxPosn);
+//        return (off64_t) -1;
+//    }
+//    return newOffset;
+//}
+///*
+// * ===========================================================================
+// *      _FileAsset
+// * ===========================================================================
+// */
+///*
+// * Constructor.
+// */
+//_FileAsset::_FileAsset(void)
+//    : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
+//{
+//    // Register the Asset with the global list here after it is fully constructed and its
+//    // vtable pointer points to this concrete type. b/31113965
+//    registerAsset(this);
+//}
+///*
+// * Destructor.  Release resources.
+// */
+//_FileAsset::~_FileAsset(void)
+//{
+//    close();
+//    // Unregister the Asset from the global list here before it is destructed and while its vtable
+//    // pointer still points to this concrete type. b/31113965
+//    unregisterAsset(this);
+//}
+///*
+// * Operate on a chunk of an uncompressed file.
+// *
+// * Zero-length chunks are allowed.
+// */
+//status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
+//{
+//    assert(mFp == NULL);    // no reopen
+//    assert(mMap == NULL);
+//    assert(fd >= 0);
+//    assert(offset >= 0);
+//    /*
+//     * Seek to end to get file length.
+//     */
+//    off64_t fileLength;
+//    fileLength = lseek64(fd, 0, SEEK_END);
+//    if (fileLength == (off64_t) -1) {
+//        // probably a bad file descriptor
+//        ALOGD("failed lseek (errno=%d)\n", errno);
+//        return UNKNOWN_ERROR;
+//    }
+//    if ((off64_t) (offset + length) > fileLength) {
+//        ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
+//            (long) offset, (long) length, (long) fileLength);
+//        return BAD_INDEX;
+//    }
+//    /* after fdopen, the fd will be closed on fclose() */
+//    mFp = fdopen(fd, "rb");
+//    if (mFp == NULL)
+//        return UNKNOWN_ERROR;
+//    mStart = offset;
+//    mLength = length;
+//    assert(mOffset == 0);
+//    /* seek the FILE* to the start of chunk */
+//    if (fseek(mFp, mStart, SEEK_SET) != 0) {
+//        assert(false);
+//    }
+//    mFileName = fileName != NULL ? strdup(fileName) : NULL;
+//    return NO_ERROR;
+//}
+///*
+// * Create the chunk from the map.
+// */
+//status_t _FileAsset::openChunk(FileMap* dataMap)
+//{
+//    assert(mFp == NULL);    // no reopen
+//    assert(mMap == NULL);
+//    assert(dataMap != NULL);
+//    mMap = dataMap;
+//    mStart = -1;            // not used
+//    mLength = dataMap->getDataLength();
+//    assert(mOffset == 0);
+//    return NO_ERROR;
+//}
+///*
+// * Read a chunk of data.
+// */
+//ssize_t _FileAsset::read(void* buf, size_t count)
+//{
+//    size_t maxLen;
+//    size_t actual;
+//    assert(mOffset >= 0 && mOffset <= mLength);
+//    if (getAccessMode() == ACCESS_BUFFER) {
+//        /*
+//         * On first access, read or map the entire file.  The caller has
+//         * requested buffer access, either because they're going to be
+//         * using the buffer or because what they're doing has appropriate
+//         * performance needs and access patterns.
+//         */
+//        if (mBuf == NULL)
+//            getBuffer(false);
+//    }
+//    /* adjust count if we're near EOF */
+//    maxLen = mLength - mOffset;
+//    if (count > maxLen)
+//        count = maxLen;
+//    if (!count)
+//        return 0;
+//    if (mMap != NULL) {
+//        /* copy from mapped area */
+//        //printf("map read\n");
+//        memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
+//        actual = count;
+//    } else if (mBuf != NULL) {
+//        /* copy from buffer */
+//        //printf("buf read\n");
+//        memcpy(buf, (char*)mBuf + mOffset, count);
+//        actual = count;
+//    } else {
+//        /* read from the file */
+//        //printf("file read\n");
+//        if (ftell(mFp) != mStart + mOffset) {
+//            ALOGE("Hosed: %ld != %ld+%ld\n",
+//                ftell(mFp), (long) mStart, (long) mOffset);
+//            assert(false);
+//        }
+//        /*
+//         * This returns 0 on error or eof.  We need to use ferror() or feof()
+//         * to tell the difference, but we don't currently have those on the
+//         * device.  However, we know how much data is *supposed* to be in the
+//         * file, so if we don't read the full amount we know something is
+//         * hosed.
+//         */
+//        actual = fread(buf, 1, count, mFp);
+//        if (actual == 0)        // something failed -- I/O error?
+//            return -1;
+//        assert(actual == count);
+//    }
+//    mOffset += actual;
+//    return actual;
+//}
+///*
+// * Seek to a new position.
+// */
+//off64_t _FileAsset::seek(off64_t offset, int whence)
+//{
+//    off64_t newPosn;
+//    off64_t actualOffset;
+//    // compute new position within chunk
+//    newPosn = handleSeek(offset, whence, mOffset, mLength);
+//    if (newPosn == (off64_t) -1)
+//        return newPosn;
+//    actualOffset = mStart + newPosn;
+//    if (mFp != NULL) {
+//        if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
+//            return (off64_t) -1;
+//    }
+//    mOffset = actualOffset - mStart;
+//    return mOffset;
+//}
+///*
+// * Close the asset.
+// */
+//void _FileAsset::close(void)
+//{
+//    if (mMap != NULL) {
+//        delete mMap;
+//        mMap = NULL;
+//    }
+//    if (mBuf != NULL) {
+//        delete[] mBuf;
+//        mBuf = NULL;
+//    }
+//    if (mFileName != NULL) {
+//        free(mFileName);
+//        mFileName = NULL;
+//    }
+//    if (mFp != NULL) {
+//        // can only be NULL when called from destructor
+//        // (otherwise we would never return this object)
+//        fclose(mFp);
+//        mFp = NULL;
+//    }
+//}
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/ResStringPool.java b/resources/src/main/java/org/robolectric/res/android/ResStringPool.java
index 0ccbf6a..8cb48f8 100644
--- a/resources/src/main/java/org/robolectric/res/android/ResStringPool.java
+++ b/resources/src/main/java/org/robolectric/res/android/ResStringPool.java
@@ -8,9 +8,11 @@
 import static org.robolectric.res.android.Errors.NO_ERROR;
 import static org.robolectric.res.android.Errors.NO_INIT;
 import static org.robolectric.res.android.Util.ALOGI;
-import static org.robolectric.res.android.Util.ALOGW;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import org.robolectric.res.android.ResXMLTree.XmlBuffer.XmlResStringPool;
 import org.robolectric.util.Strings;
 
 /**
@@ -46,7 +48,7 @@
     setTo(header, strings);
   }
 
-  private int setTo(ResStringPoolHeader header, List<String> strings) {
+  public int setTo(ResStringPoolHeader header, List<String> strings) {
     if (header == null || strings == null || strings.isEmpty()) {
       return setError(BAD_TYPE);
     }
@@ -55,6 +57,12 @@
     return setError(NO_ERROR);
   }
 
+//  public void setTo(XmlResStringPool xmlStringPool) {
+//    this.mHeader = new ResStringPoolHeader();
+//    this.mStrings = new ArrayList<>();
+//    Collections.addAll(mStrings, xmlStringPool.strings());
+//  }
+
   private int setError(int error) {
     mError = error;
     return mError;
@@ -75,7 +83,18 @@
       return mStrings.get(idx);
     }
     return null;
+  }
 
+  String stringAt(int idx, Ref<Integer> outLen) {
+    String s = stringAt(idx);
+    if (s != null && outLen != null) {
+      outLen.set(s.length());
+    }
+    return s;
+  }
+
+  public String string8At(int id, Ref<Integer> outLen) {
+    return stringAt(id, outLen);
   }
 
 //    final ResStringPool_span* styleAt(final ResStringPool_ref& ref) final;
@@ -140,6 +159,14 @@
       return mStrings.size();
     }
 
+  public boolean isUTF8() {
+    return true;
+  }
+
+  public int getError() {
+    return mError;
+  }
+
 //    int styleCount() final;
 //    int bytes() final;
 //
diff --git a/resources/src/main/java/org/robolectric/res/android/ResTable.java b/resources/src/main/java/org/robolectric/res/android/ResTable.java
index 1181bec..0a5d1d1 100644
--- a/resources/src/main/java/org/robolectric/res/android/ResTable.java
+++ b/resources/src/main/java/org/robolectric/res/android/ResTable.java
@@ -37,16 +37,16 @@
   static final int APP_PACKAGE_ID      = 0x7f;
   static final int SYS_PACKAGE_ID      = 0x01;
 
-  private static final boolean kDebugStringPoolNoisy = false;
-  private static final boolean kDebugXMLNoisy = false;
-  private static final boolean kDebugTableNoisy = false;
-  private static final boolean kDebugTableGetEntry = false;
+  static final boolean kDebugStringPoolNoisy = false;
+  static final boolean kDebugXMLNoisy = false;
+  static final boolean kDebugTableNoisy = false;
+  static final boolean kDebugTableGetEntry = false;
   static final boolean kDebugTableSuperNoisy = false;
-  private static final boolean kDebugLoadTableNoisy = false;
-  private static final boolean kDebugLoadTableSuperNoisy = false;
-  private static final boolean kDebugTableTheme = false;
-  private static final boolean kDebugResXMLTree = false;
-  private static final boolean kDebugLibNoisy = false;
+  static final boolean kDebugLoadTableNoisy = false;
+  static final boolean kDebugLoadTableSuperNoisy = false;
+  static final boolean kDebugTableTheme = false;
+  static final boolean kDebugResXMLTree = false;
+  static final boolean kDebugLibNoisy = false;
 
   private static final Object NULL = null;
   public static final bag_set SENTINEL_BAG_SET = new bag_set(1);
@@ -101,6 +101,8 @@
     byte[] buf = ByteStreams.toByteArray(is);
     ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
     Chunk.read(buffer, this, cookie);
+//    int id = mPackageGroups.values().iterator().next().id; // huh?
+//    mPackageMap[id] = (byte) (id + 1); // huh?
   }
 
 //  Errors add(final Object data, int size, final int cookie, boolean copyData) {
@@ -825,6 +827,18 @@
     return mHeaders.get(index).values;
   }
 
+  public DynamicRefTable getDynamicRefTableForCookie(int cookie) {
+    for (PackageGroup pg : mPackageGroups.values()) {
+      int M = pg.packages.size();
+      for (int j = 0; j < M; j++) {
+        if (pg.packages.get(j).header.cookie == cookie) {
+          return pg.dynamicRefTable;
+        }
+      }
+    }
+    return null;
+  }
+
   // A group of objects describing a particular resource package.
   // The first in 'package' is always the root object (from the resource
   // table that defined the package); the ones after are skins on top of it.
diff --git a/resources/src/main/java/org/robolectric/res/android/ResValue.java b/resources/src/main/java/org/robolectric/res/android/ResValue.java
index 6f45a3c..fb921a2 100644
--- a/resources/src/main/java/org/robolectric/res/android/ResValue.java
+++ b/resources/src/main/java/org/robolectric/res/android/ResValue.java
@@ -41,4 +41,9 @@
         .append("}");
     return sb.toString();
   }
+
+  public void copyFrom_dtoh(ResValue resValue) {
+    this.dataType = resValue.dataType;
+    this.data = resValue.data;
+  }
 }
diff --git a/resources/src/main/java/org/robolectric/res/android/ResXMLParser.java b/resources/src/main/java/org/robolectric/res/android/ResXMLParser.java
new file mode 100644
index 0000000..bc353a2
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ResXMLParser.java
@@ -0,0 +1,609 @@
+package org.robolectric.res.android;
+
+import static org.robolectric.res.android.Errors.BAD_TYPE;
+import static org.robolectric.res.android.Errors.NAME_NOT_FOUND;
+import static org.robolectric.res.android.Errors.NO_ERROR;
+import static org.robolectric.res.android.ResTable.kDebugStringPoolNoisy;
+import static org.robolectric.res.android.ResTable.kDebugXMLNoisy;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.BAD_DOCUMENT;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.END_DOCUMENT;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.END_NAMESPACE;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.END_TAG;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.FIRST_CHUNK_CODE;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.START_DOCUMENT;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.START_NAMESPACE;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.START_TAG;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.TEXT;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_CDATA_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_END_ELEMENT_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_END_NAMESPACE_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_FIRST_CHUNK_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_START_ELEMENT_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_START_NAMESPACE_TYPE;
+import static org.robolectric.res.android.Util.ALOGI;
+import static org.robolectric.res.android.Util.ALOGW;
+import static org.robolectric.res.android.Util.dtohl;
+import static org.robolectric.res.android.Util.dtohs;
+import static org.robolectric.res.android.Util.isTruthy;
+
+import org.robolectric.res.android.ResourceTypes.ResChunk_header;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_attrExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_attribute;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_cdataExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_endElementExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_namespaceExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_node;
+
+public class ResXMLParser {
+
+  static final int SIZEOF_RESVALUE = 8;
+  static final int SIZEOF_RESXMLTREE_NAMESPACE_EXT = 4;
+  static final int SIZEOF_RESXMLTREE_NODE = ResChunk_header.SIZEOF + 8;
+  static final int SIZEOF_RESXMLTREE_ATTR_EXT = 20;
+  static final int SIZEOF_RESXMLTREE_ATTRIBUTE = 12 + SIZEOF_RESVALUE;
+  static final int SIZEOF_RESXMLTREE_END_ELEMENT_EXT = 8;
+  static final int SIZEOF_RESXMLTREE_CDATA_EXT = 4 + SIZEOF_RESVALUE;
+  static final int SIZEOF_CHAR = 2;
+
+  public static class event_code_t {
+    public static final int BAD_DOCUMENT = -1;
+    public static final int START_DOCUMENT = 0;
+    public static final int END_DOCUMENT = 1;
+
+    public static final int FIRST_CHUNK_CODE = RES_XML_FIRST_CHUNK_TYPE;
+ 
+    public static final int START_NAMESPACE = RES_XML_START_NAMESPACE_TYPE;
+    public static final int END_NAMESPACE = RES_XML_END_NAMESPACE_TYPE;
+    public static final int START_TAG = RES_XML_START_ELEMENT_TYPE;
+    public static final int END_TAG = RES_XML_END_ELEMENT_TYPE;
+    public static final int TEXT = RES_XML_CDATA_TYPE;
+  }
+
+  ResXMLTree           mTree;
+  int                mEventCode;
+    ResXMLTree_node      mCurNode;
+    Object                 mCurExt;
+
+  public ResXMLParser(ResXMLTree tree) {
+    this.mTree = tree;
+    this.mEventCode = BAD_DOCUMENT;
+  }
+  
+  public void restart() {
+    mCurNode = null;
+    mEventCode = mTree.mError == NO_ERROR ? START_DOCUMENT : BAD_DOCUMENT;
+  }
+  
+  public ResStringPool getStrings() {
+    return mTree.mStrings;
+  }
+
+  int getEventType()
+  {
+    return mEventCode;
+  }
+
+  public int next()
+  {
+    if (mEventCode == START_DOCUMENT) {
+      mCurNode = mTree.mRootNode;
+      mCurExt = mTree.mRootExt;
+      return (mEventCode=mTree.mRootCode);
+    } else if (mEventCode >= FIRST_CHUNK_CODE) {
+      return nextNode();
+    }
+    return mEventCode;
+  }
+
+  int getCommentID()
+  {
+    return mCurNode != null ? dtohl(mCurNode.comment().index()) : -1;
+  }
+
+final String getComment(Ref<Integer> outLen)
+  {
+    int id = getCommentID();
+    return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+  }
+
+  public int getLineNumber()
+  {
+    return mCurNode != null ? dtohl(mCurNode.lineNumber()) : -1;
+  }
+
+  public int getTextID()
+  {
+    if (mEventCode == TEXT) {
+      return dtohl(((ResXMLTree_cdataExt)mCurExt).data().index());
+    }
+    return -1;
+  }
+
+final String getText(Ref<Integer> outLen)
+  {
+    int id = getTextID();
+    return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+  }
+
+  int getTextValue(ResValue outValue)
+  {
+    if (mEventCode == TEXT) {
+      outValue.copyFrom_dtoh(((ResXMLTree_cdataExt)mCurExt).typedData());
+      return SIZEOF_RESVALUE /* sizeof(Res_value) */;
+    }
+    return BAD_TYPE;
+  }
+
+  int getNamespacePrefixID()
+  {
+    if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) {
+      return dtohl(((ResXMLTree_namespaceExt)mCurExt).prefix().index());
+    }
+    return -1;
+  }
+
+final String getNamespacePrefix(Ref<Integer> outLen)
+  {
+    int id = getNamespacePrefixID();
+    //printf("prefix=%d  event=%p\n", id, mEventCode);
+    return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+  }
+
+  int getNamespaceUriID()
+  {
+    if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) {
+      return dtohl(((ResXMLTree_namespaceExt)mCurExt).uri().index());
+    }
+    return -1;
+  }
+
+final String getNamespaceUri(Ref<Integer> outLen)
+  {
+    int id = getNamespaceUriID();
+    //printf("uri=%d  event=%p\n", id, mEventCode);
+    return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+  }
+
+  int getElementNamespaceID()
+  {
+    if (mEventCode == START_TAG) {
+      return dtohl(((ResXMLTree_attrExt)mCurExt).ns().index());
+    }
+    if (mEventCode == END_TAG) {
+      return dtohl(((ResXMLTree_endElementExt)mCurExt).ns().index());
+    }
+    return -1;
+  }
+
+final String getElementNamespace(Ref<Integer> outLen)
+  {
+    int id = getElementNamespaceID();
+    return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+  }
+
+  public int getElementNameID()
+  {
+    if (mEventCode == START_TAG) {
+      return dtohl(((ResXMLTree_attrExt)mCurExt).name().index());
+    }
+    if (mEventCode == END_TAG) {
+      return dtohl(((ResXMLTree_endElementExt)mCurExt).name().index());
+    }
+    return -1;
+  }
+
+final String getElementName(Ref<Integer> outLen)
+  {
+    int id = getElementNameID();
+    return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+  }
+
+  public int getAttributeCount()
+  {
+    if (mEventCode == START_TAG) {
+      return dtohs(((ResXMLTree_attrExt)mCurExt).attributeCount());
+    }
+    return 0;
+  }
+
+  public int getAttributeNamespaceID(int idx)
+  {
+    if (mEventCode == START_TAG) {
+        ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+      if (idx < dtohs(tag.attributeCount())) {
+//            final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+//        (((final int8_t*)tag)
+//        + dtohs(tag.attributeStart())
+//            + (dtohs(tag.attributeSize())*idx));
+        ResXMLTree_attribute attr = tag.attributeAt(idx);
+        return dtohl(attr.ns().index());
+      }
+    }
+    return -2;
+  }
+
+final String getAttributeNamespace(int idx, Ref<Integer> outLen)
+  {
+    int id = getAttributeNamespaceID(idx);
+    //printf("attribute namespace=%d  idx=%d  event=%p\n", id, idx, mEventCode);
+    if (kDebugXMLNoisy) {
+      System.out.println(String.format("getAttributeNamespace 0x%zx=0x%x\n", idx, id));
+    }
+    return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+  }
+
+final String getAttributeNamespace8(int idx, Ref<Integer> outLen)
+  {
+    int id = getAttributeNamespaceID(idx);
+    //printf("attribute namespace=%d  idx=%d  event=%p\n", id, idx, mEventCode);
+    if (kDebugXMLNoisy) {
+      System.out.println(String.format("getAttributeNamespace 0x%zx=0x%x\n", idx, id));
+    }
+    return id >= 0 ? mTree.mStrings.string8At(id, outLen) : null;
+  }
+
+  public int getAttributeNameID(int idx)
+  {
+    if (mEventCode == START_TAG) {
+        ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+      if (idx < dtohs(tag.attributeCount())) {
+//            final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+//        (((final int8_t*)tag)
+//        + dtohs(tag.attributeStart())
+//            + (dtohs(tag.attributeSize())*idx));
+        ResXMLTree_attribute attr = tag.attributeAt(idx);
+        return dtohl(attr.name().index());
+      }
+    }
+    return -1;
+  }
+
+final String getAttributeName(int idx, Ref<Integer> outLen)
+  {
+    int id = getAttributeNameID(idx);
+    //printf("attribute name=%d  idx=%d  event=%p\n", id, idx, mEventCode);
+    if (kDebugXMLNoisy) {
+      System.out.println(String.format("getAttributeName 0x%zx=0x%x\n", idx, id));
+    }
+    return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+  }
+
+final String getAttributeName8(int idx, Ref<Integer> outLen)
+  {
+    int id = getAttributeNameID(idx);
+    //printf("attribute name=%d  idx=%d  event=%p\n", id, idx, mEventCode);
+    if (kDebugXMLNoisy) {
+      System.out.println(String.format("getAttributeName 0x%zx=0x%x\n", idx, id));
+    }
+    return id >= 0 ? mTree.mStrings.string8At(id, outLen) : null;
+  }
+
+  int getAttributeNameResID(int idx)
+  {
+    int id = getAttributeNameID(idx);
+    if (id >= 0 && (int)id < mTree.mNumResIds) {
+      int resId = dtohl(mTree.mResIds[id]);
+      if (mTree.mDynamicRefTable != null) {
+        Ref<Integer> resIdRef = new Ref<>(resId);
+        mTree.mDynamicRefTable.lookupResourceId(resIdRef);
+        resId = resIdRef.get();
+      }
+      return resId;
+    }
+    return 0;
+  }
+
+  int getAttributeValueStringID(int idx)
+  {
+    if (mEventCode == START_TAG) {
+        ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+      if (idx < dtohs(tag.attributeCount())) {
+//            final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+//        (((final int8_t*)tag)
+//        + dtohs(tag.attributeStart())
+//            + (dtohs(tag.attributeSize())*idx));
+        ResXMLTree_attribute attr = tag.attributeAt(idx);
+        return dtohl(attr.rawValue().index());
+      }
+    }
+    return -1;
+  }
+
+final String getAttributeStringValue(int idx, Ref<Integer> outLen)
+  {
+    int id = getAttributeValueStringID(idx);
+    if (kDebugXMLNoisy) {
+      System.out.println(String.format("getAttributeValue 0x%zx=0x%x\n", idx, id));
+    }
+    return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : null;
+  }
+
+  int getAttributeDataType(int idx)
+  {
+    if (mEventCode == START_TAG) {
+        final ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+      if (idx < dtohs(tag.attributeCount())) {
+//            final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+//        (((final int8_t*)tag)
+//        + dtohs(tag.attributeStart())
+//            + (dtohs(tag.attributeSize())*idx));
+        ResXMLTree_attribute attr = tag.attributeAt(idx);
+        int type = attr.typedValue().dataType;
+        if (type != DataType.DYNAMIC_REFERENCE.code()) {
+          return type;
+        }
+
+        // This is a dynamic reference. We adjust those references
+        // to regular references at this level, so lie to the caller.
+        return DataType.REFERENCE.code();
+      }
+    }
+    return DataType.NULL.code();
+  }
+
+  int getAttributeData(int idx)
+  {
+    if (mEventCode == START_TAG) {
+        ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+      if (idx < dtohs(tag.attributeCount())) {
+//            final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+//        (((final int8_t*)tag)
+//        + dtohs(tag.attributeStart())
+//            + (dtohs(tag.attributeSize())*idx));
+        ResXMLTree_attribute attr = tag.attributeAt(idx);
+        if (attr.typedValue().dataType != DataType.DYNAMIC_REFERENCE.code() ||
+            mTree.mDynamicRefTable == null) {
+          return dtohl(attr.typedValue().data);
+        }
+
+        Ref<Integer> data = new Ref<>(dtohl(attr.typedValue().data));
+        if (mTree.mDynamicRefTable.lookupResourceId(data) == NO_ERROR) {
+          return data.get();
+        }
+      }
+    }
+    return 0;
+  }
+
+  public int getAttributeValue(int idx, ResValue outValue)
+  {
+    if (mEventCode == START_TAG) {
+        ResXMLTree_attrExt tag = (ResXMLTree_attrExt)mCurExt;
+      if (idx < dtohs(tag.attributeCount())) {
+//            final ResXMLTree_attribute attr = (ResXMLTree_attribute)
+//        (((final int8_t*)tag)
+//        + dtohs(tag.attributeStart())
+//            + (dtohs(tag.attributeSize())*idx));
+        ResXMLTree_attribute attr = tag.attributeAt(idx);
+        outValue.copyFrom_dtoh(attr.typedValue());
+        if (mTree.mDynamicRefTable != null &&
+            mTree.mDynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) {
+          return BAD_TYPE;
+        }
+        return SIZEOF_RESVALUE /* sizeof(ResValue) */;
+      }
+    }
+    return BAD_TYPE;
+  }
+
+  int indexOfAttribute(final String ns, final String attr)
+  {
+    String nsStr = ns != null ? ns : "";
+    String attrStr = attr;
+    return indexOfAttribute(isTruthy(ns) ? nsStr : null, isTruthy(ns) ? nsStr.length() : 0,
+        attrStr, attrStr.length());
+  }
+
+  int indexOfAttribute(final String ns, int nsLen,
+                                       final String attr, int attrLen)
+  {
+    if (mEventCode == START_TAG) {
+      if (attr == null) {
+        return NAME_NOT_FOUND;
+      }
+        final int N = getAttributeCount();
+      if (mTree.mStrings.isUTF8()) {
+        String8 ns8 = null, attr8;
+        if (ns != null) {
+          ns8 = new String8(ns, nsLen);
+        }
+        attr8 = new String8(attr, attrLen);
+        if (kDebugStringPoolNoisy) {
+          ALOGI("indexOfAttribute UTF8 %s (%zu) / %s (%zu)", ns8.string(), nsLen,
+              attr8.string(), attrLen);
+        }
+        for (int i=0; i<N; i++) {
+          Ref<Integer> curNsLen = new Ref<>(0), curAttrLen = new Ref<>(0);
+                final String curNs = getAttributeNamespace8(i, curNsLen);
+                final String curAttr = getAttributeName8(i, curAttrLen);
+          if (kDebugStringPoolNoisy) {
+            ALOGI("  curNs=%s (%zu), curAttr=%s (%zu)", curNs, curNsLen, curAttr, curAttrLen);
+          }
+          if (curAttr != null && curNsLen.get() == nsLen && curAttrLen.get() == attrLen
+              && memcmp(attr8.string(), curAttr, attrLen) == 0) {
+            if (ns == null) {
+              if (curNs == null) {
+                if (kDebugStringPoolNoisy) {
+                  ALOGI("  FOUND!");
+                }
+                return i;
+              }
+            } else if (curNs != null) {
+              //printf(" -. ns=%s, curNs=%s\n",
+              //       String8(ns).string(), String8(curNs).string());
+              if (memcmp(ns8.string(), curNs, nsLen) == 0) {
+                if (kDebugStringPoolNoisy) {
+                  ALOGI("  FOUND!");
+                }
+                return i;
+              }
+            }
+          }
+        }
+      } else {
+        if (kDebugStringPoolNoisy) {
+          ALOGI("indexOfAttribute UTF16 %s (%zu) / %s (%zu)",
+              ns /*String8(ns, nsLen).string()*/, nsLen,
+              attr /*String8(attr, attrLen).string()*/, attrLen);
+        }
+        for (int i=0; i<N; i++) {
+          Ref<Integer> curNsLen = new Ref<>(0), curAttrLen = new Ref<>(0);
+                final String curNs = getAttributeNamespace(i, curNsLen);
+                final String curAttr = getAttributeName(i, curAttrLen);
+          if (kDebugStringPoolNoisy) {
+            ALOGI("  curNs=%s (%zu), curAttr=%s (%zu)",
+                curNs /*String8(curNs, curNsLen).string()*/, curNsLen,
+                curAttr /*String8(curAttr, curAttrLen).string()*/, curAttrLen);
+          }
+          if (curAttr != null && curNsLen.get() == nsLen && curAttrLen.get() == attrLen
+              && (memcmp(attr, curAttr, attrLen*SIZEOF_CHAR/*sizeof(char16_t)*/) == 0)) {
+            if (ns == null) {
+              if (curNs == null) {
+                if (kDebugStringPoolNoisy) {
+                  ALOGI("  FOUND!");
+                }
+                return i;
+              }
+            } else if (curNs != null) {
+              //printf(" -. ns=%s, curNs=%s\n",
+              //       String8(ns).string(), String8(curNs).string());
+              if (memcmp(ns, curNs, nsLen*SIZEOF_CHAR/*sizeof(char16_t)*/) == 0) {
+                if (kDebugStringPoolNoisy) {
+                  ALOGI("  FOUND!");
+                }
+                return i;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return NAME_NOT_FOUND;
+  }
+
+  private int memcmp(String s1, String s2, int len) {
+    for (int i = 0; i < len; i++) {
+      int d = s1.charAt(i) - s2.charAt(i);
+      if (d != 0) {
+        return d;
+      }
+    }
+    return 0;
+  }
+
+  int indexOfID()
+  {
+    if (mEventCode == START_TAG) {
+        final int idx = dtohs(((ResXMLTree_attrExt)mCurExt).idIndex());
+      if (idx > 0) return (idx-1);
+    }
+    return NAME_NOT_FOUND;
+  }
+
+  int indexOfClass()
+  {
+    if (mEventCode == START_TAG) {
+        final int idx = dtohs(((ResXMLTree_attrExt)mCurExt).classIndex());
+      if (idx > 0) return (idx-1);
+    }
+    return NAME_NOT_FOUND;
+  }
+
+  public int indexOfStyle()
+  {
+    if (mEventCode == START_TAG) {
+        final int idx = dtohs(((ResXMLTree_attrExt)mCurExt).styleIndex());
+      if (idx > 0) return (idx-1);
+    }
+    return NAME_NOT_FOUND;
+  }
+
+  int nextNode()
+  {
+    if (mEventCode < 0) {
+      return mEventCode;
+    }
+
+    do {
+//        final ResXMLTree_node next = (ResXMLTree_node)
+//      (((final int8_t*)mCurNode) + dtohl(mCurNode.header().size()));
+        ResXMLTree_node next = mTree.mBuffer.new XmlTreeNode(mCurNode.myOffset() + dtohl(mCurNode.header().size()));
+      if (kDebugXMLNoisy) {
+        ALOGI("Next node: prev=%p, next=%p\n", mCurNode, next);
+      }
+
+      if (next.myOffset() >= mTree.mDataLen) {
+        mCurNode = null;
+        return (mEventCode=END_DOCUMENT);
+      }
+
+      if (mTree.validateNode(next) != NO_ERROR) {
+        mCurNode = null;
+        return (mEventCode=BAD_DOCUMENT);
+      }
+
+      mCurNode = next;
+        final int headerSize = dtohs(next.header().headerSize());
+        final int totalSize = dtohl(next.header().size());
+      mCurExt = next.myOffset() + headerSize;
+      int minExtSize = 0;
+      int eventCode = dtohs(next.header().type());
+      switch ((mEventCode=eventCode)) {
+        case RES_XML_START_NAMESPACE_TYPE:
+        case RES_XML_END_NAMESPACE_TYPE:
+          minExtSize = SIZEOF_RESXMLTREE_NAMESPACE_EXT /*sizeof(ResXMLTree_namespaceExt)*/;
+          break;
+        case RES_XML_START_ELEMENT_TYPE:
+          minExtSize = SIZEOF_RESXMLTREE_ATTR_EXT /*sizeof(ResXMLTree_attrExt)*/;
+          break;
+        case RES_XML_END_ELEMENT_TYPE:
+          minExtSize = SIZEOF_RESXMLTREE_END_ELEMENT_EXT /*sizeof(ResXMLTree_endElementExt)*/;
+          break;
+        case RES_XML_CDATA_TYPE:
+          minExtSize = SIZEOF_RESXMLTREE_CDATA_EXT /*sizeof(ResXMLTree_cdataExt)*/;
+          break;
+        default:
+          ALOGW("Unknown XML block: header type %d in node at %d\n",
+              (int)dtohs(next.header().type()),
+              (next.myOffset()-mTree.mHeader.myOffset()));
+          continue;
+      }
+
+      if ((totalSize-headerSize) < minExtSize) {
+        ALOGW("Bad XML block: header type 0x%x in node at 0x%x has size %d, need %d\n",
+            (int)dtohs(next.header().type()),
+            (next.myOffset()-mTree.mHeader.myOffset()),
+        (int)(totalSize-headerSize), (int)minExtSize);
+        return (mEventCode=BAD_DOCUMENT);
+      }
+
+      //printf("CurNode=%p, CurExt=%p, headerSize=%d, minExtSize=%d\n",
+      //       mCurNode, mCurExt, headerSize, minExtSize);
+
+      return eventCode;
+    } while (true);
+  }
+
+  void getPosition(ResXMLPosition pos)
+  {
+    pos.eventCode = mEventCode;
+    pos.curNode = mCurNode;
+    pos.curExt = mCurExt;
+  }
+
+  void setPosition(final ResXMLPosition pos)
+  {
+    mEventCode = pos.eventCode;
+    mCurNode = pos.curNode;
+    mCurExt = pos.curExt;
+  }
+
+  static class ResXMLPosition
+  {
+    int                eventCode;
+        ResXMLTree_node curNode;
+        Object                 curExt;
+  };
+
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/ResXMLTree.java b/resources/src/main/java/org/robolectric/res/android/ResXMLTree.java
new file mode 100644
index 0000000..dda75cc
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ResXMLTree.java
@@ -0,0 +1,559 @@
+package org.robolectric.res.android;
+
+import static org.robolectric.res.android.Errors.BAD_TYPE;
+import static org.robolectric.res.android.Errors.NO_ERROR;
+import static org.robolectric.res.android.Errors.NO_INIT;
+import static org.robolectric.res.android.ResTable.kDebugResXMLTree;
+import static org.robolectric.res.android.ResTable.kDebugXMLNoisy;
+import static org.robolectric.res.android.ResXMLParser.SIZEOF_RESXMLTREE_ATTR_EXT;
+import static org.robolectric.res.android.ResXMLParser.SIZEOF_RESXMLTREE_NODE;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.BAD_DOCUMENT;
+import static org.robolectric.res.android.ResXMLParser.event_code_t.START_DOCUMENT;
+import static org.robolectric.res.android.ResourceTypes.RES_STRING_POOL_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_FIRST_CHUNK_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_LAST_CHUNK_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_RESOURCE_MAP_TYPE;
+import static org.robolectric.res.android.ResourceTypes.RES_XML_START_ELEMENT_TYPE;
+import static org.robolectric.res.android.ResourceTypes.validate_chunk;
+import static org.robolectric.res.android.Util.ALOGI;
+import static org.robolectric.res.android.Util.ALOGW;
+import static org.robolectric.res.android.Util.SIZEOF_INT;
+import static org.robolectric.res.android.Util.dtohl;
+import static org.robolectric.res.android.Util.dtohs;
+import static org.robolectric.res.android.Util.isTruthy;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.robolectric.res.android.Chunk.StringPoolChunk;
+import org.robolectric.res.android.ResXMLTree.XmlBuffer.XmlTreeHeader;
+import org.robolectric.res.android.ResourceTypes.ResChunk_header;
+import org.robolectric.res.android.ResourceTypes.ResStringPool_header;
+import org.robolectric.res.android.ResourceTypes.ResStringPool_ref;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_attrExt;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_attribute;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_header;
+import org.robolectric.res.android.ResourceTypes.ResXMLTree_node;
+
+public class ResXMLTree {
+
+  final DynamicRefTable mDynamicRefTable;
+  public final ResXMLParser mParser;
+
+  int                    mError;
+  byte[]                       mOwnedData;
+  XmlBuffer mBuffer;
+    XmlTreeHeader mHeader;
+  int                      mSize;
+  //    final uint8_t*              mDataEnd;
+  int mDataLen;
+  ResStringPool               mStrings = new ResStringPool();
+    int[]             mResIds;
+  int                      mNumResIds;
+    ResXMLTree_node mRootNode;
+    Object                 mRootExt;
+  int                mRootCode;
+
+  static volatile AtomicInteger gCount = new AtomicInteger(0);
+
+  public ResXMLTree(DynamicRefTable dynamicRefTable) {
+    mParser = new ResXMLParser(this);
+
+    mDynamicRefTable = dynamicRefTable;
+    mError = NO_INIT;
+    mOwnedData = null;
+
+    if (kDebugResXMLTree) {
+      ALOGI("Creating ResXMLTree %p #%d\n", this, gCount.getAndIncrement()+1);
+    }
+    mParser.restart();
+  }
+
+//  ResXMLTree() {
+//    this(null);
+//  }
+
+//  ~ResXMLTree()
+//  {
+  protected void finalize() {
+    if (kDebugResXMLTree) {
+      ALOGI("Destroying ResXMLTree in %p #%d\n", this, gCount.getAndDecrement()-1);
+    }
+    uninit();
+  }
+
+  public int setTo(byte[] data, int size, boolean copyData)
+  {
+    uninit();
+    mParser.mEventCode = START_DOCUMENT;
+
+    if (!isTruthy(data) || !isTruthy(size)) {
+      return (mError=BAD_TYPE);
+    }
+
+    if (copyData) {
+      mOwnedData = new byte[size];
+//      if (mOwnedData == null) {
+//        return (mError=NO_MEMORY);
+//      }
+//      memcpy(mOwnedData, data, size);
+      System.arraycopy(data, 0, mOwnedData, 0, size);
+      data = mOwnedData;
+    }
+
+    mBuffer = new XmlBuffer(data);
+    mHeader = mBuffer.new XmlTreeHeader(0);
+    mSize = dtohl(mHeader.header().size());
+    if (dtohs(mHeader.header().headerSize()) > mSize || mSize > size) {
+      ALOGW("Bad XML block: header size %d or total size %d is larger than data size %d\n",
+          (int)dtohs(mHeader.header().headerSize()),
+          (int)dtohl(mHeader.header().size()), (int)size);
+      mError = BAD_TYPE;
+      mParser.restart();
+      return mError;
+    }
+//    mDataEnd = ((final uint8_t*)mHeader) + mSize;
+    mDataLen = mSize;
+
+    mStrings.uninit();
+    mRootNode = null;
+    mResIds = null;
+    mNumResIds = 0;
+
+    // First look for a couple interesting chunks: the string block
+    // and first XML node.
+    ResChunk_header chunk =
+//      (final ResChunk_header*)(((final uint8_t*)mHeader) + dtohs(mHeader.header.headerSize));
+        mBuffer.new ChunkHeader(mHeader.header().headerSize());
+
+    ResChunk_header lastChunk = chunk;
+    while (chunk.myOffset() /*((final uint8_t*)chunk)*/ < (mDataLen- ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/) &&
+        chunk.myOffset() /*((final uint8_t*)chunk)*/ < (mDataLen-dtohl(chunk.size()))) {
+      int err = validate_chunk(chunk, ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/, mDataLen, "XML");
+      if (err != NO_ERROR) {
+        mError = err;
+//          goto done;
+        mParser.restart();
+        return mError;
+      }
+      final short type = dtohs(chunk.type());
+      final int size1 = dtohl(chunk.size());
+      if (kDebugXMLNoisy) {
+//        System.out.println(String.format("Scanning @ %p: type=0x%x, size=0x%zx\n",
+//            (void*)(((uintptr_t)chunk)-((uintptr_t)mHeader)), type, size1);
+      }
+      if (type == RES_STRING_POOL_TYPE) {
+        // todo: merge Chunk/interface buffer reading strategies
+        ResChunkHeader resChunkHeader = new ResChunkHeader();
+        resChunkHeader.type = chunk.type();
+        resChunkHeader.headerSize = chunk.headerSize();
+        resChunkHeader.size = chunk.size();
+        StringPoolChunk stringPoolChunk = new StringPoolChunk(mBuffer.byteBuffer, chunk.myOffset(),
+            resChunkHeader);
+//        mStrings.setTo(mBuffer.new XmlResStringPool(chunk.myOffset()));
+        int stringCount = stringPoolChunk.getStringCount();
+        List<String> stringEntries = new ArrayList<>(stringCount);
+        for (int i=0; i < stringCount; i++) {
+          stringEntries.add(stringPoolChunk.getString(i));
+        }
+
+        ResStringPoolHeader header = new ResStringPoolHeader();
+        header.flags = stringPoolChunk.getFlags();
+        header.stringCount = stringPoolChunk.getStringCount();
+        mStrings.setTo(header, stringEntries);
+      } else if (type == RES_XML_RESOURCE_MAP_TYPE) {
+//        mResIds = (final int*)
+//        (((final uint8_t*)chunk)+dtohs(chunk.headerSize()));
+        mNumResIds = (dtohl(chunk.size())-dtohs(chunk.headerSize()))/SIZEOF_INT /*sizeof(int)*/;
+        mResIds = new int[mNumResIds];
+        for (int i = 0; i < mNumResIds; i++) {
+          mResIds[i] = mBuffer.byteBuffer.getInt(chunk.myOffset() + chunk.headerSize() + i * SIZEOF_INT);
+        }
+      } else if (type >= RES_XML_FIRST_CHUNK_TYPE
+          && type <= RES_XML_LAST_CHUNK_TYPE) {
+        if (validateNode(mBuffer.new XmlTreeNode(chunk.myOffset())) != NO_ERROR) {
+          mError = BAD_TYPE;
+//          goto done;
+          mParser.restart();
+          return mError;
+        }
+        mParser.mCurNode = mBuffer.new XmlTreeNode(lastChunk.myOffset());
+        if (mParser.nextNode() == BAD_DOCUMENT) {
+          mError = BAD_TYPE;
+//          goto done;
+          mParser.restart();
+          return mError;
+        }
+        mRootNode = mParser.mCurNode;
+        mRootExt = mParser.mCurExt;
+        mRootCode = mParser.mEventCode;
+        break;
+      } else {
+        if (kDebugXMLNoisy) {
+          System.out.println("Skipping unknown chunk!\n");
+        }
+      }
+      lastChunk = chunk;
+//      chunk = (final ResChunk_header*)
+//      (((final uint8_t*)chunk) + size1);
+      chunk = mBuffer.new ChunkHeader(chunk.myOffset() + size1);
+  }
+
+    if (mRootNode == null) {
+      ALOGW("Bad XML block: no root element node found\n");
+      mError = BAD_TYPE;
+//          goto done;
+      mParser.restart();
+      return mError;
+    }
+
+    mError = mStrings.getError();
+
+    done:
+    mParser.restart();
+    return mError;
+  }
+
+  public int getError()
+  {
+    return mError;
+  }
+
+  void uninit()
+  {
+    mError = NO_INIT;
+    mStrings.uninit();
+    if (isTruthy(mOwnedData)) {
+//      free(mOwnedData);
+      mOwnedData = null;
+    }
+    mParser.restart();
+  }
+
+  int validateNode(final ResXMLTree_node node)
+  {
+    final short eventCode = dtohs(node.header().type());
+
+    int err = validate_chunk(
+        node.header(), SIZEOF_RESXMLTREE_NODE /*sizeof(ResXMLTree_node)*/,
+      mDataLen, "ResXMLTree_node");
+
+    if (err >= NO_ERROR) {
+      // Only perform additional validation on START nodes
+      if (eventCode != RES_XML_START_ELEMENT_TYPE) {
+        return NO_ERROR;
+      }
+
+        final short headerSize = dtohs(node.header().headerSize());
+        final int size = dtohl(node.header().size());
+//        final ResXMLTree_attrExt attrExt = (final ResXMLTree_attrExt*)
+//      (((final uint8_t*)node) + headerSize);
+      ResXMLTree_attrExt attrExt = mBuffer.new XmlTreeAttrExt(node.myOffset() + headerSize);
+      // check for sensical values pulled out of the stream so far...
+      if ((size >= headerSize + SIZEOF_RESXMLTREE_ATTR_EXT /*sizeof(ResXMLTree_attrExt)*/)
+          && (attrExt.myOffset() > node.myOffset())) {
+            final int attrSize = ((int)dtohs(attrExt.attributeSize()))
+            * dtohs(attrExt.attributeCount());
+        if ((dtohs(attrExt.attributeStart())+attrSize) <= (size-headerSize)) {
+          return NO_ERROR;
+        }
+        ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
+            (int)(dtohs(attrExt.attributeStart())+attrSize),
+            (int)(size-headerSize));
+      }
+        else {
+        ALOGW("Bad XML start block: node header size 0x%x, size 0x%x\n",
+            (int)headerSize, (int)size);
+      }
+      return BAD_TYPE;
+    }
+
+    return err;
+
+//    if (false) {
+//      final boolean isStart = dtohs(node.header().type()) == RES_XML_START_ELEMENT_TYPE;
+//
+//      final short headerSize = dtohs(node.header().headerSize());
+//      final int size = dtohl(node.header().size());
+//
+//      if (headerSize >= (isStart ? sizeof(ResXMLTree_attrNode) : sizeof(ResXMLTree_node))) {
+//        if (size >= headerSize) {
+//          if ((( final uint8_t*)node) <=(mDataEnd - size)){
+//            if (!isStart) {
+//              return NO_ERROR;
+//            }
+//            if ((((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount))
+//                <= (size - headerSize)) {
+//              return NO_ERROR;
+//            }
+//            ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
+//                ((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount),
+//                (int) (size - headerSize));
+//            return BAD_TYPE;
+//          }
+//          ALOGW("Bad XML block: node at 0x%x extends beyond data end 0x%x\n",
+//              (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),(int) mSize);
+//          return BAD_TYPE;
+//        }
+//        ALOGW("Bad XML block: node at 0x%x header size 0x%x smaller than total size 0x%x\n",
+//            (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),
+//        (int) headerSize, (int) size);
+//        return BAD_TYPE;
+//      }
+//      ALOGW("Bad XML block: node at 0x%x header size 0x%x too small\n",
+//          (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),
+//      (int) headerSize);
+//      return BAD_TYPE;
+//    }
+  }
+
+  public ResStringPool getStrings() {
+    return mParser.getStrings();
+  }
+
+  public int next() {
+    return mParser.next();
+  }
+
+  static class XmlBuffer {
+    private final ByteBuffer byteBuffer;
+
+    public XmlBuffer(byte[] data) {
+      this.byteBuffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+    }
+
+    private class ChunkHeader implements ResChunk_header {
+      private final int offset;
+
+      public ChunkHeader(int offset) {
+        this.offset = offset;
+      }
+
+      @Override
+      public short type() {
+        return byteBuffer.getShort(offset);
+      }
+
+      @Override
+      public short headerSize() {
+        return byteBuffer.getShort(offset + 2);
+      }
+
+      @Override
+      public int size() {
+        return byteBuffer.getInt(offset + 4);
+      }
+
+      @Override
+      public int myOffset() {
+        return offset;
+      }
+    }
+
+    class XmlResStringPool {
+      private final int offset;
+      private final ResStringPool_header header;
+
+      XmlResStringPool(int offset) {
+        this.offset = offset;
+        header = new XmlResStringPoolHeader(offset);
+      }
+
+      public List<String> strings() {
+        int stringCount = header.stringCount();
+        ArrayList<String> list = new ArrayList<>(stringCount);
+        for (int i = 0; i < stringCount; i++) {
+//          list.add(header.string(i));
+        }
+        return list;
+      }
+    }
+
+    private class XmlResStringPoolHeader implements ResStringPool_header {
+      private int offset;
+
+      public XmlResStringPoolHeader(int offset) {
+        this.offset = offset;
+      }
+
+      @Override
+      public ResChunk_header header() {
+        return new ChunkHeader(offset);
+      }
+
+      @Override
+      public int stringCount() {
+        return byteBuffer.getInt(offset + ResChunk_header.SIZEOF);
+      }
+
+      @Override
+      public int styleCount() {
+        return byteBuffer.getInt(offset + ResChunk_header.SIZEOF + 4);
+      }
+
+      @Override
+      public int flags() {
+        return byteBuffer.getInt(offset + ResChunk_header.SIZEOF + 8);
+      }
+
+      @Override
+      public int stringsStart() {
+        return byteBuffer.getInt(offset + ResChunk_header.SIZEOF + 12);
+      }
+
+      @Override
+      public int stylesStart() {
+        return byteBuffer.getInt(offset + ResChunk_header.SIZEOF + 16);
+      }
+    }
+
+    class XmlTreeHeader implements ResXMLTree_header {
+      private final int offset;
+
+      public XmlTreeHeader(int offset) {
+        this.offset = offset;
+      }
+
+      @Override
+      public ResChunk_header header() {
+        return new ChunkHeader(offset);
+      }
+
+      @Override
+      public int myOffset() {
+        return offset;
+      }
+    }
+
+    private class XmlTreeAttrExt implements ResXMLTree_attrExt {
+      private final int offset;
+
+      public XmlTreeAttrExt(int offset) {
+        this.offset = offset;
+      }
+
+      @Override
+      public ResStringPool_ref ns() {
+        return new MyResStringPoolRef(offset);
+      }
+
+      @Override
+      public ResStringPool_ref name() {
+        return new MyResStringPoolRef(offset + 4);
+      }
+
+      @Override
+      public short attributeStart() {
+        return byteBuffer.getShort(offset + 8);
+      }
+
+      @Override
+      public short attributeSize() {
+        return byteBuffer.getShort(offset + 10);
+      }
+
+      @Override
+      public short attributeCount() {
+        return byteBuffer.getShort(offset + 12);
+      }
+
+      @Override
+      public short idIndex() {
+        return byteBuffer.getShort(offset + 14);
+      }
+
+      @Override
+      public short classIndex() {
+        return byteBuffer.getShort(offset + 16);
+      }
+
+      @Override
+      public short styleIndex() {
+        return byteBuffer.getShort(offset + 18);
+      }
+
+      @Override
+      public ResXMLTree_attribute attributeAt(int idx) {
+        return new ResXmlTreeAttribute(offset + SIZEOF_RESXMLTREE_ATTR_EXT + SIZEOF_RESXMLTREE_ATTR_EXT * idx);
+      }
+
+      @Override
+      public int myOffset() {
+        return offset;
+      }
+    }
+
+    private class ResXmlTreeAttribute implements ResXMLTree_attribute {
+      private final int offset;
+
+      public ResXmlTreeAttribute(int offset) {
+        this.offset = offset;
+      }
+
+      @Override
+      public ResStringPool_ref ns() {
+        return new MyResStringPoolRef(offset);
+      }
+
+      @Override
+      public ResStringPool_ref name() {
+        return new MyResStringPoolRef(offset + 4);
+      }
+
+      @Override
+      public ResStringPool_ref rawValue() {
+        return new MyResStringPoolRef(offset + 8);
+      }
+
+      @Override
+      public ResValue typedValue() {
+        int dataType = byteBuffer.getInt(offset + 12);
+        int data = byteBuffer.getInt(offset + 16);
+        return new ResValue(dataType, data);
+      }
+    }
+
+    class XmlTreeNode implements ResXMLTree_node {
+      private final int offset;
+
+      public XmlTreeNode(int offset) {
+        this.offset = offset;
+      }
+
+      @Override
+      public ResChunk_header header() {
+        return new ChunkHeader(offset);
+      }
+
+      @Override
+      public int lineNumber() {
+        return byteBuffer.getInt(offset + 8);
+      }
+
+      @Override
+      public ResStringPool_ref comment() {
+        return new MyResStringPoolRef(offset + 12);
+      }
+
+      @Override
+      public int myOffset() {
+        return offset;
+      }
+    }
+
+    private class MyResStringPoolRef implements ResStringPool_ref {
+      private int offset;
+
+      private MyResStringPoolRef(int offset) {
+        this.offset = offset;
+      }
+
+      @Override
+      public int index() {
+        return byteBuffer.getInt(offset);
+      }
+    }
+  }
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java b/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java
new file mode 100644
index 0000000..e8a9187
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ResourceTypes.java
@@ -0,0 +1,318 @@
+package org.robolectric.res.android;
+
+import static org.robolectric.res.android.Errors.BAD_TYPE;
+import static org.robolectric.res.android.Errors.NO_ERROR;
+import static org.robolectric.res.android.Util.ALOGW;
+import static org.robolectric.res.android.Util.dtohl;
+import static org.robolectric.res.android.Util.dtohs;
+
+public class ResourceTypes {
+  static int validate_chunk(ResChunk_header chunk,
+      int minSize,
+      int dataLen,
+      String name)
+  {
+    final short headerSize = dtohs(chunk.headerSize());
+    final int size = dtohl(chunk.size());
+
+    if (headerSize >= minSize) {
+      if (headerSize <= size) {
+        if (((headerSize|size)&0x3) == 0) {
+          if (size <= (dataLen)) {
+            return NO_ERROR;
+          }
+          ALOGW("%s data size 0x%x extends beyond resource end.",
+              name, size /*, (dataEnd-((const uint8_t*)chunk))*/);
+          return BAD_TYPE;
+        }
+        ALOGW("%s size 0x%x or headerSize 0x%x is not on an integer boundary.",
+            name, (int)size, (int)headerSize);
+        return BAD_TYPE;
+      }
+      ALOGW("%s size 0x%x is smaller than header size 0x%x.",
+          name, size, headerSize);
+      return BAD_TYPE;
+    }
+    ALOGW("%s header size 0x%04x is too small.",
+        name, headerSize);
+    return BAD_TYPE;
+  }
+
+  interface WithOffset {
+    int myOffset();
+  }
+
+  /** ********************************************************************
+   *  Base Types
+   *
+   *  These are standard types that are shared between multiple specific
+   *  resource types.
+   *
+   *********************************************************************** */
+
+  /**
+   * Header that appears at the front of every data chunk in a resource.
+   */
+  interface ResChunk_header extends WithOffset
+  {
+    int SIZEOF = 8;
+
+    // Type identifier for this chunk.  The meaning of this value depends
+    // on the containing chunk.
+    short type();
+
+    // Size of the chunk header (in bytes).  Adding this value to
+    // the address of the chunk allows you to find its associated data
+    // (if any).
+    short headerSize();
+
+    // Total size of this chunk (in bytes).  This is the chunkSize plus
+    // the size of any data associated with the chunk.  Adding this value
+    // to the chunk allows you to completely skip its contents (including
+    // any child chunks).  If this value is the same as chunkSize, there is
+    // no data associated with the chunk.
+    int size();
+  };
+
+  public static final int RES_NULL_TYPE               = 0x0000;
+  public static final int RES_STRING_POOL_TYPE        = 0x0001;
+  public static final int RES_TABLE_TYPE              = 0x0002;
+  public static final int RES_XML_TYPE                = 0x0003;
+
+  // Chunk types in RES_XML_TYPE
+  public static final int RES_XML_FIRST_CHUNK_TYPE    = 0x0100;
+  public static final int RES_XML_START_NAMESPACE_TYPE= 0x0100;
+  public static final int RES_XML_END_NAMESPACE_TYPE  = 0x0101;
+  public static final int RES_XML_START_ELEMENT_TYPE  = 0x0102;
+  public static final int RES_XML_END_ELEMENT_TYPE    = 0x0103;
+  public static final int RES_XML_CDATA_TYPE          = 0x0104;
+  public static final int RES_XML_LAST_CHUNK_TYPE     = 0x017f;
+  // This contains a uint32_t array mapping strings in the string
+  // pool back to resource identifiers.  It is optional.
+  public static final int RES_XML_RESOURCE_MAP_TYPE   = 0x0180;
+ 
+  // Chunk types in RES_TABLE_TYPE
+  public static final int RES_TABLE_PACKAGE_TYPE      = 0x0200;
+  public static final int RES_TABLE_TYPE_TYPE         = 0x0201;
+  public static final int RES_TABLE_TYPE_SPEC_TYPE    = 0x0202;
+  public static final int RES_TABLE_LIBRARY_TYPE      = 0x0203;
+
+  /**
+   * Definition for a pool of strings.  The data of this chunk is an
+   * array of uint32_t providing indices into the pool, relative to
+   * stringsStart.  At stringsStart are all of the UTF-16 strings
+   * concatenated together; each starts with a uint16_t of the string's
+   * length and each ends with a 0x0000 terminator.  If a string is >
+   * 32767 characters, the high bit of the length is set meaning to take
+   * those 15 bits as a high word and it will be followed by another
+   * uint16_t containing the low word.
+   *
+   * If styleCount is not zero, then immediately following the array of
+   * uint32_t indices into the string table is another array of indices
+   * into a style table starting at stylesStart.  Each entry in the
+   * style table is an array of ResStringPool_span structures.
+   */
+  interface ResStringPool_header
+  {
+    ResChunk_header header();
+
+    // Number of strings in this pool (number of uint32_t indices that follow
+    // in the data).
+    int stringCount();
+
+    // Number of style span arrays in the pool (number of uint32_t indices
+    // follow the string indices).
+    int styleCount();
+
+    // Flags.
+//    enum {
+    // If set, the string index is sorted by the string values (based
+    // on strcmp16()).
+    public static final int SORTED_FLAG = 1<<0;
+
+        // String pool is encoded in UTF-8
+        public static final int UTF8_FLAG = 1<<8;
+//  };
+    int flags();
+
+    // Index from header of the string data.
+    int stringsStart();
+
+    // Index from header of the style data.
+    int stylesStart();
+  };
+
+  /**
+   * This structure defines a span of style information associated with
+   * a string in the pool.
+   */
+  interface ResStringPool_span
+  {
+//    enum {
+    public static final int END = 0xFFFFFFFF;
+//  };
+
+    // This is the name of the span -- that is, the name of the XML
+    // tag that defined it.  The special value END (0xFFFFFFFF) indicates
+    // the end of an array of spans.
+    ResStringPool_ref name();
+
+    // The range of characters in the string that this span applies to.
+    int firstChar();
+    int lastChar();
+  };
+
+
+  /** ********************************************************************
+   *  XML Tree
+   *
+   *  Binary representation of an XML document.  This is designed to
+   *  express everything in an XML document, in a form that is much
+   *  easier to parse on the device.
+   *
+   *********************************************************************** */
+
+  /**
+   * XML tree header.  This appears at the front of an XML tree,
+   * describing its content.  It is followed by a flat array of
+   * ResXMLTree_node structures; the hierarchy of the XML document
+   * is described by the occurrance of RES_XML_START_ELEMENT_TYPE
+   * and corresponding RES_XML_END_ELEMENT_TYPE nodes in the array.
+   */
+  interface ResXMLTree_header extends WithOffset
+  {
+    ResChunk_header header();
+  };
+
+  /**
+   * Basic XML tree node.  A single item in the XML document.  Extended info
+   * about the node can be found after header.headerSize.
+   */
+  interface ResXMLTree_node extends WithOffset
+  {
+    ResChunk_header header();
+
+    // Line number in original source file at which this element appeared.
+    int lineNumber();
+
+    // Optional XML comment that was associated with this element; -1 if none.
+    ResStringPool_ref comment();
+  };
+
+  /**
+   * Extended XML tree node for CDATA tags -- includes the CDATA string.
+   * Appears header.headerSize bytes after a ResXMLTree_node.
+   */
+  interface ResXMLTree_cdataExt
+  {
+    // The raw CDATA character data.
+    ResStringPool_ref data();
+
+    // The typed value of the character data if this is a CDATA node.
+    ResValue typedData();
+  };
+
+  /**
+   * Extended XML tree node for namespace start/end nodes.
+   * Appears header.headerSize bytes after a ResXMLTree_node.
+   */
+  interface ResXMLTree_namespaceExt
+  {
+    // The prefix of the namespace.
+    ResStringPool_ref prefix();
+
+    // The URI of the namespace.
+    ResStringPool_ref uri();
+  };
+
+  /**
+   * Extended XML tree node for element start/end nodes.
+   * Appears header.headerSize bytes after a ResXMLTree_node.
+   */
+  interface ResXMLTree_endElementExt
+  {
+    // String of the full namespace of this element.
+    ResStringPool_ref ns();
+
+    // String name of this node if it is an ELEMENT; the raw
+    // character data if this is a CDATA node.
+    ResStringPool_ref name();
+  };
+
+  /**
+   * Extended XML tree node for start tags -- includes attribute
+   * information.
+   * Appears header.headerSize bytes after a ResXMLTree_node.
+   */
+  interface ResXMLTree_attrExt extends WithOffset
+  {
+    // String of the full namespace of this element.
+    ResStringPool_ref ns();
+
+    // String name of this node if it is an ELEMENT; the raw
+    // character data if this is a CDATA node.
+    ResStringPool_ref name();
+
+    // Byte offset from the start of this structure where the attributes start.
+    short attributeStart();
+
+    // Size of the ResXMLTree_attribute structures that follow.
+    short attributeSize();
+
+    // Number of attributes associated with an ELEMENT.  These are
+    // available as an array of ResXMLTree_attribute structures
+    // immediately following this node.
+    short attributeCount();
+
+    // Index (1-based) of the "id" attribute. 0 if none.
+    short idIndex();
+
+    // Index (1-based) of the "class" attribute. 0 if none.
+    short classIndex();
+
+    // Index (1-based) of the "style" attribute. 0 if none.
+    short styleIndex();
+
+    ResXMLTree_attribute attributeAt(int idx);
+  };
+
+  interface ResXMLTree_attribute
+  {
+    // Namespace of this attribute.
+    ResStringPool_ref ns();
+
+    // Name of this attribute.
+    ResStringPool_ref name();
+
+    // The original raw string value of this attribute.
+    ResStringPool_ref rawValue();
+
+    // Processesd typed value of this attribute.
+    ResValue typedValue();
+  };
+
+  /**
+   *  This is a reference to a unique entry (a ResTable_entry structure)
+   *  in a resource table.  The value is structured as: 0xpptteeee,
+   *  where pp is the package index, tt is the type index in that
+   *  package, and eeee is the entry index in that type.  The package
+   *  and type values start at 1 for the first item, to help catch cases
+   *  where they have not been supplied.
+   */
+  interface ResTable_ref
+  {
+    int ident();
+  };
+
+  /**
+   * Reference to a string in a string pool.
+   */
+  interface ResStringPool_ref
+  {
+    // Index into the string pool table (uint32_t-offset from the indices
+    // immediately after ResStringPool_header) at which to find the location
+    // of the string data in the pool.
+    int index();
+  };
+
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/String8.java b/resources/src/main/java/org/robolectric/res/android/String8.java
index 08a6591..f0391e1 100644
--- a/resources/src/main/java/org/robolectric/res/android/String8.java
+++ b/resources/src/main/java/org/robolectric/res/android/String8.java
@@ -20,7 +20,11 @@
     this(path.string());
   }
 
-//size_t String8::length() const
+  public String8(String value, int len) {
+    this(value.substring(0, len));
+  }
+
+  //size_t String8::length() const
 //{
 //    return SharedBuffer::sizeFromData(mString)-1;
 //}
diff --git a/resources/src/main/java/org/robolectric/res/android/Util.java b/resources/src/main/java/org/robolectric/res/android/Util.java
index 4b64a35..37a3f68 100644
--- a/resources/src/main/java/org/robolectric/res/android/Util.java
+++ b/resources/src/main/java/org/robolectric/res/android/Util.java
@@ -40,6 +40,10 @@
     return o != null;
   }
 
+  static void ALOGD(String message, Object... args) {
+    System.out.println("DEBUG: " + String.format(message, args));
+  }
+
   static void ALOGW(String message, Object... args) {
     System.out.println("WARN: " + String.format(message, args));
   }
diff --git a/resources/src/main/java/org/robolectric/res/android/ZipArchiveHandle.java b/resources/src/main/java/org/robolectric/res/android/ZipArchiveHandle.java
new file mode 100644
index 0000000..b170db9
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ZipArchiveHandle.java
@@ -0,0 +1,11 @@
+package org.robolectric.res.android;
+
+import java.util.zip.ZipFile;
+
+public class ZipArchiveHandle {
+  final ZipFile zipFile;
+
+  public ZipArchiveHandle(ZipFile zipFile) {
+    this.zipFile = zipFile;
+  }
+}
diff --git a/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java b/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java
new file mode 100644
index 0000000..389fa49
--- /dev/null
+++ b/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java
@@ -0,0 +1,285 @@
+package org.robolectric.res.android;
+
+import static org.robolectric.res.android.Errors.NAME_NOT_FOUND;
+import static org.robolectric.res.android.Errors.NO_ERROR;
+import static org.robolectric.res.android.Util.ALOGW;
+import static org.robolectric.res.android.Util.isTruthy;
+
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class ZipFileRO {
+
+  final int kCompressStored = 0;
+  final int kCompressDeflated = 8;
+
+  final ZipArchiveHandle mHandle;
+  final String mFileName;
+
+  ZipFileRO(ZipArchiveHandle handle, String fileName) {
+    this.mHandle = handle;
+    this.mFileName = fileName;
+  }
+
+  static class ZipEntryRO {
+        ZipEntry entry;
+    String name;
+    Object cookie;
+
+    ZipEntryRO() {
+    }
+
+    //    ~ZipEntryRO() {
+    protected void finalize() {
+//      EndIteration(cookie);
+    }
+
+//    private:
+//    ZipEntryRO(final ZipEntryRO& other);
+//    ZipEntryRO& operator=(final ZipEntryRO& other);
+  };
+
+//  ~ZipFileRO() {
+  protected void finalize() {
+    CloseArchive(mHandle);
+//    free(mFileName);
+  }
+
+  private static int OpenArchive(String zipFileName, Ref<ZipArchiveHandle> mHandle) {
+    try {
+      mHandle.set(new ZipArchiveHandle(new ZipFile(zipFileName)));
+      return NO_ERROR;
+    } catch (IOException e) {
+      return NAME_NOT_FOUND;
+    }
+  }
+
+  private static void CloseArchive(ZipArchiveHandle mHandle) {
+    throw new UnsupportedOperationException();
+  }
+
+  private static String ErrorCodeString(int error) {
+    return "error " + error;
+  }
+
+  private int FindEntry(ZipArchiveHandle mHandle, String name, Ref<ZipEntry> zipEntryRef) {
+    ZipEntry entry = mHandle.zipFile.getEntry(name);
+    zipEntryRef.set(entry);
+    if (entry == null) {
+      return NAME_NOT_FOUND;
+    }
+    return NO_ERROR;
+  }
+
+  /*
+ * Open the specified file read-only.  We memory-map the entire thing and
+ * close the file before returning.
+ */
+/* static */
+  static ZipFileRO open(final String zipFileName)
+  {
+    Ref<ZipArchiveHandle> handle = new Ref<>(null);
+    final int error = OpenArchive(zipFileName, handle);
+    if (isTruthy(error)) {
+      ALOGW("Error opening archive %s: %s", zipFileName, ErrorCodeString(error));
+      CloseArchive(handle.get());
+      return null;
+    }
+
+    return new ZipFileRO(handle.get(), zipFileName);
+  }
+
+  org.robolectric.res.android.ZipFileRO.ZipEntryRO findEntryByName(final String entryName)
+  {
+    ZipEntryRO data = new ZipEntryRO();
+
+    data.name = String(entryName);
+
+    Ref<ZipEntry> zipEntryRef = new Ref<>(data.entry);
+    final int error = FindEntry(mHandle, data.name, zipEntryRef);
+    if (isTruthy(error)) {
+      return null;
+    }
+
+    data.entry = zipEntryRef.get();
+    return data;
+  }
+
+  /*
+   * Get the useful fields from the zip entry.
+   *
+   * Returns "false" if the offsets to the fields or the contents of the fields
+   * appear to be bogus.
+   */
+  boolean getEntryInfo(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Ref<Short> pMethod,
+      Ref<Long> pUncompLen, Ref<Long> pCompLen, Ref<Long> pOffset,
+      Ref<Long> pModWhen, Ref<Long> pCrc32)
+  {
+    final ZipEntryRO zipEntry = /*reinterpret_cast<ZipEntryRO*>*/(entry);
+    final ZipEntry ze = zipEntry.entry;
+
+    if (pMethod != null) {
+      pMethod.set((short) ze.getMethod());
+    }
+    if (pUncompLen != null) {
+        pUncompLen.set(ze.getSize()); // uncompressed_length
+    }
+    if (pCompLen != null) {
+        pCompLen.set(ze.getCompressedSize());
+    }
+    if (pOffset != null) {
+      throw new UnsupportedOperationException("Figure out offset");
+      //        pOffset = ze.offset;
+    }
+    if (pModWhen != null) {
+        pModWhen.set(ze.getLastModifiedTime().toMillis());
+    }
+    if (pCrc32 != null) {
+      pCrc32.set(ze.getCrc());
+    }
+
+    return true;
+  }
+
+  boolean startIteration(Object cookie) {
+    return startIteration(cookie, null, null);
+  }
+
+  boolean startIteration(/* void** */ Object cookie, final String prefix, final String suffix)
+  {
+    throw new UnsupportedOperationException("Implememnt me");
+//    ZipEntryRO* ze = new ZipEntryRO;
+//    String pe(prefix ? prefix : "");
+//    String se(suffix ? suffix : "");
+//    int error = StartIteration(mHandle, &(ze.cookie),
+//    prefix ? &pe : null,
+//      suffix ? &se : null);
+//    if (error) {
+//      ALOGW("Could not start iteration over %s: %s", mFileName, ErrorCodeString(error));
+//      delete ze;
+//      return false;
+//    }
+//
+//    *cookie = ze;
+//    return true;
+  }
+
+  org.robolectric.res.android.ZipFileRO.ZipEntryRO nextEntry(/*void* */ Object cookie)
+  {
+    throw new UnsupportedOperationException("Implememnt me");
+//    ZipEntryRO ze = /*reinterpret_cast<ZipEntryRO*>*/(ZipEntryRO) cookie;
+//    int error = Next(ze.cookie, &(ze.entry), &(ze.name));
+//    if (error) {
+//      if (error != -1) {
+//        ALOGW("Error iteration over %s: %s", mFileName, ErrorCodeString(error));
+//      }
+//      return null;
+//    }
+//
+//    return &(ze.entry);
+  }
+
+  void endIteration(/*void**/ Object cookie)
+  {
+//    delete reinterpret_cast<ZipEntryRO*>(cookie);
+  }
+
+  void releaseEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry)
+  {
+//    delete reinterpret_cast<ZipEntryRO*>(entry);
+  }
+
+  /*
+   * Copy the entry's filename to the buffer.
+   */
+  int getEntryFileName(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, String buffer, int bufLen)
+  {
+    throw new UnsupportedOperationException("Implememnt me");
+
+//    final ZipEntryRO* zipEntry = reinterpret_cast<ZipEntryRO*>(entry);
+//    final uint16_t requiredSize = zipEntry.name.name_length + 1;
+//
+//    if (bufLen < requiredSize) {
+//      ALOGW("Buffer too short, requires %d bytes for entry name", requiredSize);
+//      return requiredSize;
+//    }
+//
+//    memcpy(buffer, zipEntry.name.name, requiredSize - 1);
+//    buffer[requiredSize - 1] = '\0';
+//
+//    return 0;
+  }
+
+/*
+ * Create a new FileMap object that spans the data in "entry".
+ */
+  /*FileMap*/ ZipFileRO(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry)
+  {
+    throw new UnsupportedOperationException("Implememnt me");
+
+//    final ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry);
+//    final ZipEntry& ze = zipEntry.entry;
+//    int fd = GetFileDescriptor(mHandle);
+//    size_t actualLen = 0;
+//
+//    if (ze.method == kCompressStored) {
+//      actualLen = ze.uncompressed_length;
+//    } else {
+//      actualLen = ze.compressed_length;
+//    }
+//
+//    FileMap* newMap = new FileMap();
+//    if (!newMap.create(mFileName, fd, ze.offset, actualLen, true)) {
+//      delete newMap;
+//      return null;
+//    }
+//
+//    return newMap;
+  }
+
+  /*
+   * Uncompress an entry, in its entirety, into the provided output buffer.
+   *
+   * This doesn't verify the data's CRC, which might be useful for
+   * uncompressed data.  The caller should be able to manage it.
+   */
+  boolean uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Object buffer, int size)
+  {
+    throw new UnsupportedOperationException("Implememnt me");
+//    ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry);
+//    final int error = ExtractToMemory(mHandle, &(zipEntry.entry),
+//    (uint8_t*) buffer, size);
+//    if (error) {
+//      ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error));
+//      return false;
+//    }
+//
+//    return true;
+  }
+
+  /*
+   * Uncompress an entry, in its entirety, to an open file descriptor.
+   *
+   * This doesn't verify the data's CRC, but probably should.
+   */
+  boolean uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, int fd)
+  {
+    throw new UnsupportedOperationException("Implememnt me");
+//    ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry);
+//    final int error = ExtractEntryToFile(mHandle, &(zipEntry.entry), fd);
+//    if (error) {
+//      ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error));
+//      return false;
+//    }
+//
+//    return true;
+  }
+
+  static String String(String string) {
+    return string;
+  }
+
+  static class FileMap {
+  }
+}
diff --git a/robolectric/src/main/java/org/robolectric/res/builder/DefaultPackageManager.java b/robolectric/src/main/java/org/robolectric/res/builder/DefaultPackageManager.java
index 639e93a..d05f349 100644
--- a/robolectric/src/main/java/org/robolectric/res/builder/DefaultPackageManager.java
+++ b/robolectric/src/main/java/org/robolectric/res/builder/DefaultPackageManager.java
@@ -751,7 +751,8 @@
     TempDirectory tempDirectory = RuntimeEnvironment.getTempDirectory();
 
     if (androidManifest != null) {
-      applicationInfo.sourceDir = androidManifest.getResDirectory().getParent().toString();
+//      applicationInfo.sourceDir = androidManifest.getResDirectory().getParent().toString();
+      applicationInfo.sourceDir = "build/resources/test/resources.ap_"; // todo get this from AndroidManifest
     } else {
       applicationInfo.sourceDir = tempDirectory.createIfNotExists(applicationInfo.packageName + "-sourceDir").toAbsolutePath().toString();
     }
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
index 93c8c31..4d36058 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
@@ -461,7 +461,7 @@
     assertThat(Resources.getSystem().getIdentifier("copy", "string", TestUtil.TEST_PACKAGE)).isEqualTo(0);
   }
 
-  @Test
+  @Test @Config(sdk = 25) // todo: unpin sdk
   public void testGetXml() throws Exception {
     XmlResourceParser parser = resources.getXml(R.xml.preferences);
     assertThat(parser).isNotNull();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
index f41eb3d..d7de705 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
@@ -3,13 +3,13 @@
 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
 import static android.os.Build.VERSION_CODES.LOLLIPOP;
 import static org.robolectric.res.android.Errors.BAD_INDEX;
+import static org.robolectric.res.android.Errors.NO_ERROR;
 import static org.robolectric.shadow.api.Shadow.directlyOn;
 import static org.robolectric.shadow.api.Shadow.invokeConstructor;
 
 import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.os.Build.VERSION_CODES;
 import android.os.ParcelFileDescriptor;
@@ -26,11 +26,13 @@
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.RealObject;
 import org.robolectric.annotation.Resetter;
-import org.robolectric.res.android.BagAttributeFinder;
 import org.robolectric.res.android.Asset;
 import org.robolectric.res.android.Asset.AccessMode;
+import org.robolectric.res.android.BagAttributeFinder;
 import org.robolectric.res.android.CppAssetManager;
 import org.robolectric.res.android.DataType;
+import org.robolectric.res.android.DynamicRefTable;
+import org.robolectric.res.android.FileAsset;
 import org.robolectric.res.android.Ref;
 import org.robolectric.res.android.ResStringPool;
 import org.robolectric.res.android.ResTable;
@@ -39,6 +41,7 @@
 import org.robolectric.res.android.ResTableTheme;
 import org.robolectric.res.android.ResValue;
 import org.robolectric.res.android.ResXMLParser;
+import org.robolectric.res.android.ResXMLTree;
 import org.robolectric.res.android.String8;
 import org.robolectric.res.android.XmlAttributeFinder;
 import org.robolectric.shadow.api.Shadow;
@@ -231,13 +234,6 @@
         ClassParameter.from(String.class, fileName));
   }
 
-  public XmlResourceParser loadXmlResourceParser(int resId, String type)
-      throws Resources.NotFoundException {
-    return directlyOn(realObject, AssetManager.class, "loadXmlResourceParser",
-        ClassParameter.from(int.class, resId),
-        ClassParameter.from(String.class, type));
-  }
-
   @HiddenApi
   @Implementation
   public int addAssetPath(String path) {
@@ -436,8 +432,8 @@
       return -1;
     }
     AccessMode mode = AccessMode.values()[accessMode];
-    if (mode != Asset.AccessMode.ACCESS_UNKNOWN && mode != Asset.AccessMode.ACCESS_RANDOM
-        && mode != Asset.AccessMode.ACCESS_STREAMING && mode != Asset.AccessMode.ACCESS_BUFFER) {
+    if (mode != FileAsset.AccessMode.ACCESS_UNKNOWN && mode != FileAsset.AccessMode.ACCESS_RANDOM
+        && mode != FileAsset.AccessMode.ACCESS_STREAMING && mode != FileAsset.AccessMode.ACCESS_BUFFER) {
       throw new IllegalArgumentException("Bad access mode");
     }
     Asset a = isTruthy(cookie)
@@ -450,6 +446,8 @@
     synchronized (assets) {
       assetId = nextAssetId++;
       assets.put(assetId, a);
+      // todo: something better than this
+      a.onClose = () -> destroyAsset(assetId);
     }
     //printf("Created Asset Stream: %p\n", a);
     return assetId;
@@ -749,7 +747,7 @@
     Ref<Integer> styleBagTypeSetFlags = new Ref(0);
     if (xmlParser != null) {
       int idx = xmlParser.indexOfStyle();
-      if (idx >= 0 && xmlParser.getAttributeValue(idx, value) >= 0) {
+      if (idx >= 0 && xmlParser.getAttributeValue(idx, value.get()) >= 0) {
         if (value.get().dataType == DataType.ATTRIBUTE.code()) {
           if (theme.getAttribute(value.get().data, value, styleBagTypeSetFlags) < 0) {
             value.get().dataType = DataType.NULL.code();
@@ -818,7 +816,7 @@
       if (xmlAttrIdx != xmlAttrEnd) {
         // We found the attribute we were looking for.
         block = kXmlBlock;
-        xmlParser.getAttributeValue(xmlAttrIdx, value);
+        xmlParser.getAttributeValue(xmlAttrIdx, value.get());
         if (kDebugStyles) {
           ALOGI(". From XML: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
         }
@@ -937,7 +935,7 @@
 
   @Implementation
   @HiddenApi
-  public static final boolean resolveAttrs(long theme,
+  public static boolean resolveAttrs(long theme,
       int defStyleAttr, int defStyleRes, int[] inValues,
       int[] inAttrs, int[] outValues, int[] outIndices) {
     throw new UnsupportedOperationException("not yet implemented");
@@ -1183,8 +1181,46 @@
 //  /*package*/@HiddenApi @Implementation public static final @NativeConfig
 //  int getThemeChangingConfigurations(long theme);
 
-  @HiddenApi @Implementation public final long openXmlAssetNative(int cookie, String fileName){
-    throw new UnsupportedOperationException("not yet implemented");
+  @HiddenApi @Implementation public final long openXmlAssetNative(int cookie, String fileName)
+      throws FileNotFoundException {
+    CppAssetManager am = assetManagerForJavaObject();
+    if (am == null) {
+      return 0;
+    }
+
+    ALOGV("openXmlAsset in %s (Java object %s)\n", am, ShadowArscAssetManager.class);
+
+    String fileName8 = fileName;
+    if (fileName8 == null) {
+      return 0;
+    }
+
+    int assetCookie = cookie;
+    Asset a;
+    if (isTruthy(assetCookie)) {
+      a = am.openNonAsset(assetCookie, fileName8, AccessMode.ACCESS_BUFFER);
+    } else {
+      Ref<Integer> assetCookieRef = new Ref<>(assetCookie);
+      a = am.openNonAsset(fileName8, AccessMode.ACCESS_BUFFER, assetCookieRef);
+      assetCookie = assetCookieRef.get();
+    }
+
+    if (a == null) {
+      throw new FileNotFoundException(fileName8);
+    }
+
+    final DynamicRefTable dynamicRefTable =
+        am.getResources().getDynamicRefTableForCookie(assetCookie);
+    ResXMLTree block = new ResXMLTree(dynamicRefTable);
+    int err = block.setTo(a.getBuffer(true), a.getLength(), true);
+    a.close();
+//    delete a;
+
+    if (err != NO_ERROR) {
+      throw new FileNotFoundException("Corrupt XML binary file");
+    }
+
+    return ShadowXmlBlock.RES_XML_TREES.getNativeObjectId(block);
   }
 
   @HiddenApi @Implementation public final String[] getArrayStringResource(int arrayResId){
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
index 07dbd80..e6ba84b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
@@ -208,16 +208,16 @@
     return displayMetrics;
   }
 
-  @HiddenApi @Implementation
-  public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
-    ShadowAssetManager shadowAssetManager = legacyShadowOf(realResources.getAssets());
-    return shadowAssetManager.loadXmlResourceParser(resId, type);
-  }
-
-  @HiddenApi @Implementation
-  public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException {
-    return loadXmlResourceParser(id, type);
-  }
+//  @HiddenApi @Implementation
+//  public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
+//    ShadowAssetManager shadowAssetManager = legacyShadowOf(realResources.getAssets());
+//    return shadowAssetManager.loadXmlResourceParser(resId, type);
+//  }
+//
+//  @HiddenApi @Implementation
+//  public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException {
+//    return loadXmlResourceParser(id, type);
+//  }
 
   @Implements(value = Resources.Theme.class)
   public static class ShadowTheme {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java
index 8f9c638..5df6d3e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java
@@ -188,17 +188,17 @@
     return displayMetrics;
   }
 
-  @HiddenApi
-  @Implementation
-  public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
-    ShadowAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets());
-    return shadowAssetManager.loadXmlResourceParser(resId, type);
-  }
-
-  @HiddenApi @Implementation
-  public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException {
-    return loadXmlResourceParser(id, type);
-  }
+//  @HiddenApi
+//  @Implementation
+//  public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
+//    ShadowAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets());
+//    return shadowAssetManager.loadXmlResourceParser(resId, type);
+//  }
+//
+//  @HiddenApi @Implementation
+//  public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException {
+//    return loadXmlResourceParser(id, type);
+//  }
 
   @Implements(value = ResourcesImpl.ThemeImpl.class, minSdk = N, isInAndroidSdk = false)
   public static class ShadowThemeImpl {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStringBlock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStringBlock.java
index e00754e..0dcfad4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStringBlock.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStringBlock.java
@@ -3,11 +3,10 @@
 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
 import static android.os.Build.VERSION_CODES.LOLLIPOP;
 
-import com.google.common.base.Preconditions;
-import com.sun.org.glassfish.external.statistics.annotations.Reset;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.RealObject;
+import org.robolectric.annotation.Resetter;
 import org.robolectric.res.android.ResStringPool;
 
 @Implements(className = "android.content.res.StringBlock", isInAndroidSdk = false)
@@ -16,7 +15,7 @@
   @RealObject
   Object realObject;
 
-  private static NativeObjRegistry<ResStringPool> nativeStringPoolRegistry = new NativeObjRegistry();
+  private static NativeObjRegistry<ResStringPool> nativeStringPoolRegistry = new NativeObjRegistry<>();
 
   static long getNativePointer(ResStringPool tableStringBlock) {
     return nativeStringPoolRegistry.getNativeObjectId(tableStringBlock);
@@ -58,8 +57,8 @@
     return null;
   }
 
-  @Reset
-  public void reset() {
+  @Resetter
+  public static void reset() {
     nativeStringPoolRegistry.clear();
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowXmlBlock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowXmlBlock.java
new file mode 100644
index 0000000..5d28aa7
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowXmlBlock.java
@@ -0,0 +1,202 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.res.android.Errors.NO_ERROR;
+
+import android.os.Build.VERSION_CODES;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.res.android.ResStringPool;
+import org.robolectric.res.android.ResXMLParser;
+import org.robolectric.res.android.ResXMLTree;
+import org.xmlpull.v1.XmlPullParserException;
+
+@Implements(className = "android.content.res.XmlBlock", isInAndroidSdk = false)
+public class ShadowXmlBlock {
+  static final NativeObjRegistry<ResXMLTree> RES_XML_TREES = new NativeObjRegistry<>();
+  private static final NativeObjRegistry<ResXMLParser> RES_XML_PARSERS = new NativeObjRegistry<>();
+  private static final NativeObjRegistry<ResStringPool> RES_STRING_POOLS = new NativeObjRegistry<>();
+
+  @Implementation
+  public static long nativeCreate(byte[] bArray, int off, int len) {
+    if (bArray == null) {
+      throw new NullPointerException();
+    }
+
+    int bLen = bArray.length;
+    if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
+      throw new IndexOutOfBoundsException();
+    }
+
+    // todo: optimize
+    byte[] b = new byte[len];
+    System.arraycopy(bArray, off, b, 0, len);
+
+    ResXMLTree osb = new ResXMLTree(null);
+    osb.setTo(b, len, true);
+//    env->ReleaseByteArrayElements(bArray, b, 0);
+
+    if (osb.getError() != NO_ERROR) {
+      throw new IllegalArgumentException();
+    }
+
+    return RES_XML_TREES.getNativeObjectId(osb);
+  }
+
+  // todo: implement pre-Lollipop
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static long nativeGetStringBlock(long obj) {
+    ResXMLTree osb = RES_XML_TREES.getNativeObject(obj);
+//    if (osb == NULL) {
+//      jniThrowNullPointerException(env, NULL);
+//      return 0;
+//    }
+
+    return RES_STRING_POOLS.getNativeObjectId(osb.getStrings());
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static long nativeCreateParseState(long obj) {
+    ResXMLTree osb = RES_XML_TREES.getNativeObject(obj);
+//    if (osb == NULL) {
+//      jniThrowNullPointerException(env, NULL);
+//      return 0;
+//    }
+
+//    ResXMLParser st = new ResXMLParser(osb);
+    ResXMLParser st = osb.mParser;
+//    if (st == NULL) {
+//      jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+//      return 0;
+//    }
+
+    st.restart();
+
+    return RES_XML_PARSERS.getNativeObjectId(st);
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeNext(long state) throws XmlPullParserException {
+    ResXMLTree st = RES_XML_TREES.getNativeObject(state);
+    if (st == null) {
+      return ResXMLParser.event_code_t.END_DOCUMENT;
+    }
+
+    do {
+      int code = st.next();
+      switch (code) {
+        case ResXMLParser.event_code_t.START_TAG:
+          return 2;
+        case ResXMLParser.event_code_t.END_TAG:
+          return 3;
+        case ResXMLParser.event_code_t.TEXT:
+          return 4;
+        case ResXMLParser.event_code_t.START_DOCUMENT:
+          return 0;
+        case ResXMLParser.event_code_t.END_DOCUMENT:
+          return 1;
+        case ResXMLParser.event_code_t.BAD_DOCUMENT:
+//                goto bad;
+          throw new XmlPullParserException("Corrupt XML binary file");
+        default:
+          break;
+      }
+
+    } while (true);
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetNamespace(long state) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetName(long state) {
+    ResXMLParser resXMLParser = getResXMLParser(state);
+    return resXMLParser.getElementNameID();
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetText(long state) {
+    ResXMLParser resXMLParser = getResXMLParser(state);
+    return resXMLParser.getTextID();
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetLineNumber(long state) {
+    ResXMLParser resXMLParser = getResXMLParser(state);
+    return resXMLParser.getLineNumber();
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetAttributeCount(long state) {
+    ResXMLParser resXMLParser = getResXMLParser(state);
+    return resXMLParser.getAttributeCount();
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetAttributeNamespace(long state, int idx) {
+    ResXMLParser resXMLParser = getResXMLParser(state);
+    return resXMLParser.getAttributeNamespaceID(idx);
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetAttributeName(long state, int idx) {
+    ResXMLParser resXMLParser = getResXMLParser(state);
+    return resXMLParser.getAttributeNameID(idx);
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetAttributeResource(long state, int idx) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetAttributeDataType(long state, int idx) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetAttributeData(long state, int idx) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetAttributeStringValue(long state, int idx) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetIdAttribute(long state) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetClassAttribute(long state) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetStyleAttribute(long state) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static int nativeGetAttributeIndex(long state, String namespace, String name) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static void nativeDestroyParseState(long state) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+  public static void nativeDestroy(long obj) {
+    throw new UnsupportedOperationException("implement me");
+  }
+
+  private static ResXMLParser getResXMLParser(long state) {
+    return RES_XML_PARSERS.getNativeObject(state);
+  }
+}