stagefright: add HEIF support

- add HEIF support in MPEG4Extractor.

- decode HEIF image or tiled image in StagefrightMetadataRetriever's
  getFrameAt() api and convert to requested color format.

bug: 64077740

Test: cts-tradefed run cts-dev --module CtsMediaTestCases --compatibility:module-arg CtsMediaTestCases:include-annotation:android.platform.test.annotations.RequiresDevice

Change-Id: Id4b26f1584ca6219073ffb0167f208e1c3bf8aa2
diff --git a/include/private/media/VideoFrame.h b/include/private/media/VideoFrame.h
index 51050cd..a9d4dd1 100644
--- a/include/private/media/VideoFrame.h
+++ b/include/private/media/VideoFrame.h
@@ -30,14 +30,41 @@
 class VideoFrame
 {
 public:
-    VideoFrame(): mWidth(0), mHeight(0), mDisplayWidth(0), mDisplayHeight(0), mSize(0),
-            mRotationAngle(0), mData(0) {}
+    // Construct a VideoFrame object with the specified parameters,
+    // will allocate frame buffer if |allocate| is set to true, will
+    // allocate buffer to hold ICC data if |iccData| and |iccSize|
+    // indicate its presence.
+    VideoFrame(uint32_t width, uint32_t height,
+            uint32_t displayWidth, uint32_t displayHeight,
+            uint32_t angle, uint32_t bpp, bool allocate,
+            const void *iccData, size_t iccSize):
+        mWidth(width), mHeight(height),
+        mDisplayWidth(displayWidth), mDisplayHeight(displayHeight),
+        mRotationAngle(angle), mBytesPerPixel(bpp), mRowBytes(bpp * width),
+        mSize(0), mIccSize(0), mReserved(0), mData(0), mIccData(0) {
+        if (allocate) {
+            mSize = mRowBytes * mHeight;
+            mData = new uint8_t[mSize];
+            if (mData == NULL) {
+                mSize = 0;
+            }
+        }
 
+        if (iccData != NULL && iccSize > 0) {
+            mIccSize = iccSize;
+            mIccData = new uint8_t[iccSize];
+            if (mIccData != NULL) {
+                memcpy(mIccData, iccData, iccSize);
+            } else {
+                mIccSize = 0;
+            }
+        }
+    }
+
+    // Deep copy of both the information fields and the frame data
     VideoFrame(const VideoFrame& copy) {
-        mWidth = copy.mWidth;
-        mHeight = copy.mHeight;
-        mDisplayWidth = copy.mDisplayWidth;
-        mDisplayHeight = copy.mDisplayHeight;
+        copyInfoOnly(copy);
+
         mSize = copy.mSize;
         mData = NULL;  // initialize it first
         if (mSize > 0 && copy.mData != NULL) {
@@ -48,26 +75,99 @@
                 mSize = 0;
             }
         }
-        mRotationAngle = copy.mRotationAngle;
+
+        mIccSize = copy.mIccSize;
+        mIccData = NULL;  // initialize it first
+        if (mIccSize > 0 && copy.mIccData != NULL) {
+            mIccData = new uint8_t[mIccSize];
+            if (mIccData != NULL) {
+                memcpy(mIccData, copy.mIccData, mIccSize);
+            } else {
+                mIccSize = 0;
+            }
+        }
     }
 
     ~VideoFrame() {
         if (mData != 0) {
             delete[] mData;
         }
+        if (mIccData != 0) {
+            delete[] mIccData;
+        }
+    }
+
+    // Copy |copy| to a flattened VideoFrame in IMemory, 'this' must point to
+    // a chunk of memory back by IMemory of size at least getFlattenedSize()
+    // of |copy|.
+    void copyFlattened(const VideoFrame& copy) {
+        copyInfoOnly(copy);
+
+        mSize = copy.mSize;
+        mData = NULL;  // initialize it first
+        if (copy.mSize > 0 && copy.mData != NULL) {
+            memcpy(getFlattenedData(), copy.mData, copy.mSize);
+        }
+
+        mIccSize = copy.mIccSize;
+        mIccData = NULL;  // initialize it first
+        if (copy.mIccSize > 0 && copy.mIccData != NULL) {
+            memcpy(getFlattenedIccData(), copy.mIccData, copy.mIccSize);
+        }
+    }
+
+    // Calculate the flattened size to put it in IMemory
+    size_t getFlattenedSize() const {
+        return sizeof(VideoFrame) + mSize + mIccSize;
+    }
+
+    // Get the pointer to the frame data in a flattened VideoFrame in IMemory
+    uint8_t* getFlattenedData() const {
+        return (uint8_t*)this + sizeof(VideoFrame);
+    }
+
+    // Get the pointer to the ICC data in a flattened VideoFrame in IMemory
+    uint8_t* getFlattenedIccData() const {
+        return (uint8_t*)this + sizeof(VideoFrame) + mSize;
     }
 
     // Intentional public access modifier:
-    uint32_t mWidth;
-    uint32_t mHeight;
-    uint32_t mDisplayWidth;
-    uint32_t mDisplayHeight;
+    uint32_t mWidth;           // Decoded image width before rotation
+    uint32_t mHeight;          // Decoded image height before rotation
+    uint32_t mDisplayWidth;    // Display width before rotation
+    uint32_t mDisplayHeight;   // Display height before rotation
+    int32_t  mRotationAngle;   // Rotation angle, clockwise, should be multiple of 90
+    uint32_t mBytesPerPixel;   // Number of bytes per pixel
+    uint32_t mRowBytes;        // Number of bytes per row before rotation
     uint32_t mSize;            // Number of bytes in mData
-    int32_t  mRotationAngle;   // rotation angle, clockwise, should be multiple of 90
-    // mData should be 64 bit aligned to prevent additional padding
+    uint32_t mIccSize;         // Number of bytes in mIccData
+    uint32_t mReserved;        // (padding to make mData 64-bit aligned)
+
+    // mData should be 64-bit aligned to prevent additional padding
     uint8_t* mData;            // Actual binary data
-    // pad structure so it's the same size on 64 bit and 32 bit
+    // pad structure so it's the same size on 64-bit and 32-bit
     char     mPadding[8 - sizeof(mData)];
+
+    // mIccData should be 64-bit aligned to prevent additional padding
+    uint8_t* mIccData;            // Actual binary data
+    // pad structure so it's the same size on 64-bit and 32-bit
+    char     mIccPadding[8 - sizeof(mIccData)];
+
+private:
+    //
+    // Utility methods used only within VideoFrame struct
+    //
+
+    // Copy the information fields only
+    void copyInfoOnly(const VideoFrame& copy) {
+        mWidth = copy.mWidth;
+        mHeight = copy.mHeight;
+        mDisplayWidth = copy.mDisplayWidth;
+        mDisplayHeight = copy.mDisplayHeight;
+        mRotationAngle = copy.mRotationAngle;
+        mBytesPerPixel = copy.mBytesPerPixel;
+        mRowBytes = copy.mRowBytes;
+    }
 };
 
 }; // namespace android
diff --git a/media/libmedia/include/media/mediametadataretriever.h b/media/libmedia/include/media/mediametadataretriever.h
index aa2c868..65c266b 100644
--- a/media/libmedia/include/media/mediametadataretriever.h
+++ b/media/libmedia/include/media/mediametadataretriever.h
@@ -79,7 +79,7 @@
     status_t setDataSource(
             const sp<IDataSource>& dataSource, const char *mime = NULL);
     sp<IMemory> getFrameAtTime(int64_t timeUs, int option,
-            int colorFormat = 0, bool metaOnly = false);
+            int colorFormat = HAL_PIXEL_FORMAT_RGB_565, bool metaOnly = false);
     sp<IMemory> extractAlbumArt();
     const char* extractMetadata(int keyCode);
 
diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
index 47c5aaf..5a468f3 100644
--- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp
+++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
@@ -211,7 +211,7 @@
         ALOGE("failed to capture a video frame");
         return NULL;
     }
-    size_t size = sizeof(VideoFrame) + frame->mSize;
+    size_t size = frame->getFlattenedSize();
     sp<MemoryHeapBase> heap = new MemoryHeapBase(size, 0, "MetadataRetrieverClient");
     if (heap == NULL) {
         ALOGE("failed to create MemoryDealer");
@@ -225,16 +225,7 @@
         return NULL;
     }
     VideoFrame *frameCopy = static_cast<VideoFrame *>(mThumbnail->pointer());
-    frameCopy->mWidth = frame->mWidth;
-    frameCopy->mHeight = frame->mHeight;
-    frameCopy->mDisplayWidth = frame->mDisplayWidth;
-    frameCopy->mDisplayHeight = frame->mDisplayHeight;
-    frameCopy->mSize = frame->mSize;
-    frameCopy->mRotationAngle = frame->mRotationAngle;
-    ALOGV("rotation: %d", frameCopy->mRotationAngle);
-    frameCopy->mData = (uint8_t *)frameCopy + sizeof(VideoFrame);
-    memcpy(frameCopy->mData, frame->mData, frame->mSize);
-    frameCopy->mData = 0;
+    frameCopy->copyFlattened(*frame);
     delete frame;  // Fix memory leakage
     return mThumbnail;
 }
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 19973bd..348abf8 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -30,6 +30,7 @@
         "FrameRenderTracker.cpp",
         "HTTPBase.cpp",
         "HevcUtils.cpp",
+        "ItemTable.cpp",
         "JPEGSource.cpp",
         "MP3Extractor.cpp",
         "MPEG2TSWriter.cpp",
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
index a5760d1..c22053e 100644
--- a/media/libstagefright/DataSource.cpp
+++ b/media/libstagefright/DataSource.cpp
@@ -92,6 +92,48 @@
     return true;
 }
 
+bool DataSource::getUInt16Var(off64_t offset, uint16_t *x, size_t size) {
+    if (size == 2) {
+        return getUInt16(offset, x);
+    }
+    if (size == 1) {
+        uint8_t tmp;
+        if (readAt(offset, &tmp, 1) == 1) {
+            *x = tmp;
+            return true;
+        }
+    }
+    return false;
+}
+
+bool DataSource::getUInt32Var(off64_t offset, uint32_t *x, size_t size) {
+    if (size == 4) {
+        return getUInt32(offset, x);
+    }
+    if (size == 2) {
+        uint16_t tmp;
+        if (getUInt16(offset, &tmp)) {
+            *x = tmp;
+            return true;
+        }
+    }
+    return false;
+}
+
+bool DataSource::getUInt64Var(off64_t offset, uint64_t *x, size_t size) {
+    if (size == 8) {
+        return getUInt64(offset, x);
+    }
+    if (size == 4) {
+        uint32_t tmp;
+        if (getUInt32(offset, &tmp)) {
+            *x = tmp;
+            return true;
+        }
+    }
+    return false;
+}
+
 status_t DataSource::getSize(off64_t *size) {
     *size = 0;
 
diff --git a/media/libstagefright/ItemTable.cpp b/media/libstagefright/ItemTable.cpp
new file mode 100644
index 0000000..b7ff21b
--- /dev/null
+++ b/media/libstagefright/ItemTable.cpp
@@ -0,0 +1,1544 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ItemTable"
+//#define LOG_NDEBUG 0
+
+#include <include/ItemTable.h>
+#include <media/MediaDefs.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <utils/Log.h>
+
+namespace android {
+
+namespace heif {
+
+/////////////////////////////////////////////////////////////////////
+//
+//  struct to keep track of one image item
+//
+
+struct ImageItem {
+    friend struct ItemReference;
+    friend struct ItemProperty;
+
+    ImageItem() : ImageItem(0) {}
+    ImageItem(uint32_t _type) : type(_type),
+            rows(0), columns(0), width(0), height(0), rotation(0),
+            offset(0), size(0), nextTileIndex(0) {}
+
+    bool isGrid() const {
+        return type == FOURCC('g', 'r', 'i', 'd');
+    }
+
+    status_t getNextTileItemId(uint32_t *nextTileItemId, bool reset) {
+        if (reset) {
+            nextTileIndex = 0;
+        }
+        if (nextTileIndex >= dimgRefs.size()) {
+            return ERROR_END_OF_STREAM;
+        }
+        *nextTileItemId = dimgRefs[nextTileIndex++];
+        return OK;
+    }
+
+    uint32_t type;
+    int32_t rows;
+    int32_t columns;
+    int32_t width;
+    int32_t height;
+    int32_t rotation;
+    off64_t offset;
+    size_t size;
+    sp<ABuffer> hvcc;
+    sp<ABuffer> icc;
+
+    Vector<uint32_t> thumbnails;
+    Vector<uint32_t> dimgRefs;
+    size_t nextTileIndex;
+};
+
+
+/////////////////////////////////////////////////////////////////////
+//
+//  ISO boxes
+//
+
+struct Box {
+protected:
+    Box(const sp<DataSource> source, uint32_t type) :
+        mDataSource(source), mType(type) {}
+
+    virtual ~Box() {}
+
+    virtual status_t onChunkData(
+            uint32_t /*type*/, off64_t /*offset*/, size_t /*size*/) {
+        return OK;
+    }
+
+    inline uint32_t type() const { return mType; }
+
+    inline sp<DataSource> source() const { return mDataSource; }
+
+    status_t parseChunk(off64_t *offset);
+
+    status_t parseChunks(off64_t offset, size_t size);
+
+private:
+    sp<DataSource> mDataSource;
+    uint32_t mType;
+};
+
+status_t Box::parseChunk(off64_t *offset) {
+    if (*offset < 0) {
+        ALOGE("b/23540914");
+        return ERROR_MALFORMED;
+    }
+    uint32_t hdr[2];
+    if (mDataSource->readAt(*offset, hdr, 8) < 8) {
+        return ERROR_IO;
+    }
+    uint64_t chunk_size = ntohl(hdr[0]);
+    int32_t chunk_type = ntohl(hdr[1]);
+    off64_t data_offset = *offset + 8;
+
+    if (chunk_size == 1) {
+        if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
+            return ERROR_IO;
+        }
+        chunk_size = ntoh64(chunk_size);
+        data_offset += 8;
+
+        if (chunk_size < 16) {
+            // The smallest valid chunk is 16 bytes long in this case.
+            return ERROR_MALFORMED;
+        }
+    } else if (chunk_size == 0) {
+        // This shouldn't happen since we should never be top level
+        ALOGE("invalid chunk size 0 for non-top level box");
+        return ERROR_MALFORMED;
+    } else if (chunk_size < 8) {
+        // The smallest valid chunk is 8 bytes long.
+        ALOGE("invalid chunk size: %lld", (long long)chunk_size);
+        return ERROR_MALFORMED;
+    }
+
+    char chunk[5];
+    MakeFourCCString(chunk_type, chunk);
+    ALOGV("chunk: %s @ %lld", chunk, (long long)*offset);
+
+    off64_t chunk_data_size = chunk_size - (data_offset - *offset);
+    if (chunk_data_size < 0) {
+        ALOGE("b/23540914");
+        return ERROR_MALFORMED;
+    }
+
+    status_t err = onChunkData(chunk_type, data_offset, chunk_data_size);
+
+    if (err != OK) {
+        return err;
+    }
+    *offset += chunk_size;
+    return OK;
+}
+
+status_t Box::parseChunks(off64_t offset, size_t size) {
+    off64_t stopOffset = offset + size;
+    while (offset < stopOffset) {
+        status_t err = parseChunk(&offset);
+        if (err != OK) {
+            return err;
+        }
+    }
+    if (offset != stopOffset) {
+        return ERROR_MALFORMED;
+    }
+    return OK;
+}
+
+///////////////////////////////////////////////////////////////////////
+
+struct FullBox : public Box {
+protected:
+    FullBox(const sp<DataSource> source, uint32_t type) :
+        Box(source, type), mVersion(0), mFlags(0) {}
+
+    inline uint8_t version() const { return mVersion; }
+
+    inline uint32_t flags() const { return mFlags; }
+
+    status_t parseFullBoxHeader(off64_t *offset, size_t *size);
+
+private:
+    uint8_t mVersion;
+    uint32_t mFlags;
+};
+
+status_t FullBox::parseFullBoxHeader(off64_t *offset, size_t *size) {
+    if (*size < 4) {
+        return ERROR_MALFORMED;
+    }
+    if (!source()->readAt(*offset, &mVersion, 1)) {
+        return ERROR_IO;
+    }
+    if (!source()->getUInt24(*offset + 1, &mFlags)) {
+        return ERROR_IO;
+    }
+    *offset += 4;
+    *size -= 4;
+    return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+//  PrimaryImage box
+//
+
+struct PitmBox : public FullBox {
+    PitmBox(const sp<DataSource> source) :
+        FullBox(source, FOURCC('p', 'i', 't', 'm')) {}
+
+    status_t parse(off64_t offset, size_t size, uint32_t *primaryItemId);
+};
+
+status_t PitmBox::parse(off64_t offset, size_t size, uint32_t *primaryItemId) {
+    status_t err = parseFullBoxHeader(&offset, &size);
+    if (err != OK) {
+        return err;
+    }
+
+    size_t itemIdSize = (version() == 0) ? 2 : 4;
+    if (size < itemIdSize) {
+        return ERROR_MALFORMED;
+    }
+    uint32_t itemId;
+    if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) {
+        return ERROR_IO;
+    }
+
+    ALOGV("primary id %d", itemId);
+    *primaryItemId = itemId;
+
+    return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+//  ItemLocation related boxes
+//
+
+struct ExtentEntry {
+    uint64_t extentIndex;
+    uint64_t extentOffset;
+    uint64_t extentLength;
+};
+
+struct ItemLoc {
+    ItemLoc() : ItemLoc(0, 0, 0, 0) {}
+    ItemLoc(uint32_t item_id, uint16_t construction_method,
+            uint16_t data_reference_index, uint64_t base_offset) :
+        itemId(item_id),
+        constructionMethod(construction_method),
+        dataReferenceIndex(data_reference_index),
+        baseOffset(base_offset) {}
+
+    void addExtent(const ExtentEntry& extent) {
+        extents.push_back(extent);
+    }
+
+    status_t getLoc(off64_t *offset, size_t *size,
+            off64_t idatOffset, size_t idatSize) const {
+        // TODO: fix extent handling, fix constructionMethod = 2
+        CHECK(extents.size() == 1);
+        if (constructionMethod == 0) {
+            *offset = baseOffset + extents[0].extentOffset;
+            *size = extents[0].extentLength;
+            return OK;
+        } else if (constructionMethod == 1) {
+            if (baseOffset + extents[0].extentOffset + extents[0].extentLength
+                    > idatSize) {
+                return ERROR_MALFORMED;
+            }
+            *offset = baseOffset + extents[0].extentOffset + idatOffset;
+            *size = extents[0].extentLength;
+            return OK;
+        }
+        return ERROR_UNSUPPORTED;
+    }
+
+    // parsed info
+    uint32_t itemId;
+    uint16_t constructionMethod;
+    uint16_t dataReferenceIndex;
+    off64_t baseOffset;
+    Vector<ExtentEntry> extents;
+};
+
+struct IlocBox : public FullBox {
+    IlocBox(const sp<DataSource> source, KeyedVector<uint32_t, ItemLoc> *itemLocs) :
+        FullBox(source, FOURCC('i', 'l', 'o', 'c')),
+        mItemLocs(itemLocs), mHasConstructMethod1(false) {}
+
+    status_t parse(off64_t offset, size_t size);
+
+    bool hasConstructMethod1() { return mHasConstructMethod1; }
+
+private:
+    static bool isSizeFieldValid(uint32_t offset_size) {
+        return offset_size == 0 || offset_size == 4 || offset_size == 8;
+    }
+    KeyedVector<uint32_t, ItemLoc> *mItemLocs;
+    bool mHasConstructMethod1;
+};
+
+status_t IlocBox::parse(off64_t offset, size_t size) {
+    status_t err = parseFullBoxHeader(&offset, &size);
+    if (err != OK) {
+        return err;
+    }
+    if (version() > 2) {
+        ALOGE("%s: invalid version %d", __FUNCTION__, version());
+        return ERROR_MALFORMED;
+    }
+
+    if (size < 2) {
+        return ERROR_MALFORMED;
+    }
+    uint8_t offset_size;
+    if (!source()->readAt(offset++, &offset_size, 1)) {
+        return ERROR_IO;
+    }
+    uint8_t length_size = (offset_size & 0xF);
+    offset_size >>= 4;
+
+    uint8_t base_offset_size;
+    if (!source()->readAt(offset++, &base_offset_size, 1)) {
+        return ERROR_IO;
+    }
+    uint8_t index_size = 0;
+    if (version() == 1 || version() == 2) {
+        index_size = (base_offset_size & 0xF);
+    }
+    base_offset_size >>= 4;
+    size -= 2;
+
+    if (!isSizeFieldValid(offset_size)
+            || !isSizeFieldValid(length_size)
+            || !isSizeFieldValid(base_offset_size)
+            || !isSizeFieldValid((index_size))) {
+        ALOGE("%s: offset size not valid: %d, %d, %d, %d", __FUNCTION__,
+                offset_size, length_size, base_offset_size, index_size);
+        return ERROR_MALFORMED;
+    }
+
+    uint32_t item_count;
+    size_t itemFieldSize = version() < 2 ? 2 : 4;
+    if (size < itemFieldSize) {
+        return ERROR_MALFORMED;
+    }
+    if (!source()->getUInt32Var(offset, &item_count, itemFieldSize)) {
+        return ERROR_IO;
+    }
+
+    ALOGV("item_count %lld", (long long) item_count);
+    offset += itemFieldSize;
+    size -= itemFieldSize;
+
+    for (size_t i = 0; i < item_count; i++) {
+        uint32_t item_id;
+        if (!source()->getUInt32Var(offset, &item_id, itemFieldSize)) {
+            return ERROR_IO;
+        }
+        ALOGV("item[%zu]: id %lld", i, (long long)item_id);
+        offset += itemFieldSize;
+
+        uint8_t construction_method = 0;
+        if (version() == 1 || version() == 2) {
+            uint8_t buf[2];
+            if (!source()->readAt(offset, buf, 2)) {
+                return ERROR_IO;
+            }
+            construction_method = (buf[1] & 0xF);
+            ALOGV("construction_method %d", construction_method);
+            if (construction_method == 1) {
+                mHasConstructMethod1 = true;
+            }
+
+            offset += 2;
+        }
+
+        uint16_t data_reference_index;
+        if (!source()->getUInt16(offset, &data_reference_index)) {
+            return ERROR_IO;
+        }
+        ALOGV("data_reference_index %d", data_reference_index);
+        if (data_reference_index != 0) {
+            // we don't support reference to other files
+            return ERROR_UNSUPPORTED;
+        }
+        offset += 2;
+
+        uint64_t base_offset = 0;
+        if (base_offset_size != 0) {
+            if (!source()->getUInt64Var(offset, &base_offset, base_offset_size)) {
+                return ERROR_IO;
+            }
+            offset += base_offset_size;
+        }
+        ALOGV("base_offset %lld", (long long) base_offset);
+
+        ssize_t index = mItemLocs->add(item_id, ItemLoc(
+                item_id, construction_method, data_reference_index, base_offset));
+        ItemLoc &item = mItemLocs->editValueAt(index);
+
+        uint16_t extent_count;
+        if (!source()->getUInt16(offset, &extent_count)) {
+            return ERROR_IO;
+        }
+        ALOGV("extent_count %d", extent_count);
+
+        if (extent_count > 1 && (offset_size == 0 || length_size == 0)) {
+            // if the item is dividec into more than one extents, offset and
+            // length must be present.
+            return ERROR_MALFORMED;
+        }
+        offset += 2;
+
+        for (size_t j = 0; j < extent_count; j++) {
+            uint64_t extent_index = 1; // default=1
+            if ((version() == 1 || version() == 2) && (index_size > 0)) {
+                if (!source()->getUInt64Var(offset, &extent_index, index_size)) {
+                    return ERROR_IO;
+                }
+                // TODO: add support for this mode
+                offset += index_size;
+                ALOGV("extent_index %lld", (long long)extent_index);
+            }
+
+            uint64_t extent_offset = 0; // default=0
+            if (offset_size > 0) {
+                if (!source()->getUInt64Var(offset, &extent_offset, offset_size)) {
+                    return ERROR_IO;
+                }
+                offset += offset_size;
+            }
+            ALOGV("extent_offset %lld", (long long)extent_offset);
+
+            uint64_t extent_length = 0; // this indicates full length of file
+            if (length_size > 0) {
+                if (!source()->getUInt64Var(offset, &extent_length, length_size)) {
+                    return ERROR_IO;
+                }
+                offset += length_size;
+            }
+            ALOGV("extent_length %lld", (long long)extent_length);
+
+            item.addExtent({ extent_index, extent_offset, extent_length });
+        }
+    }
+    return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+//  ItemReference related boxes
+//
+
+struct ItemReference : public Box, public RefBase {
+    ItemReference(const sp<DataSource> source, uint32_t type, uint32_t itemIdSize) :
+        Box(source, type), mItemId(0), mRefIdSize(itemIdSize) {}
+
+    status_t parse(off64_t offset, size_t size);
+
+    uint32_t itemId() { return mItemId; }
+
+    void apply(KeyedVector<uint32_t, ImageItem> &itemIdToImageMap) const {
+        ssize_t imageIndex = itemIdToImageMap.indexOfKey(mItemId);
+
+        // ignore non-image items
+        if (imageIndex < 0) {
+            return;
+        }
+
+        ALOGV("attach reference type 0x%x to item id %d)", type(), mItemId);
+
+        if (type() == FOURCC('d', 'i', 'm', 'g')) {
+            ImageItem &image = itemIdToImageMap.editValueAt(imageIndex);
+            if (!image.dimgRefs.empty()) {
+                ALOGW("dimgRefs if not clean!");
+            }
+            image.dimgRefs.appendVector(mRefs);
+        } else if (type() == FOURCC('t', 'h', 'm', 'b')) {
+            for (size_t i = 0; i < mRefs.size(); i++) {
+                imageIndex = itemIdToImageMap.indexOfKey(mRefs[i]);
+
+                // ignore non-image items
+                if (imageIndex < 0) {
+                    continue;
+                }
+                ALOGV("Image item id %d uses thumbnail item id %d", mRefs[i], mItemId);
+                ImageItem &image = itemIdToImageMap.editValueAt(imageIndex);
+                if (!image.thumbnails.empty()) {
+                    ALOGW("already has thumbnails!");
+                }
+                image.thumbnails.push_back(mItemId);
+            }
+        } else {
+            ALOGW("ignoring unsupported ref type 0x%x", type());
+        }
+    }
+
+private:
+    uint32_t mItemId;
+    uint32_t mRefIdSize;
+    Vector<uint32_t> mRefs;
+
+    DISALLOW_EVIL_CONSTRUCTORS(ItemReference);
+};
+
+status_t ItemReference::parse(off64_t offset, size_t size) {
+    if (size < mRefIdSize + 2) {
+        return ERROR_MALFORMED;
+    }
+    if (!source()->getUInt32Var(offset, &mItemId, mRefIdSize)) {
+        return ERROR_IO;
+    }
+    offset += mRefIdSize;
+
+    uint16_t count;
+    if (!source()->getUInt16(offset, &count)) {
+        return ERROR_IO;
+    }
+    offset += 2;
+    size -= (mRefIdSize + 2);
+
+    if (size < count * mRefIdSize) {
+        return ERROR_MALFORMED;
+    }
+
+    for (size_t i = 0; i < count; i++) {
+        uint32_t refItemId;
+        if (!source()->getUInt32Var(offset, &refItemId, mRefIdSize)) {
+            return ERROR_IO;
+        }
+        offset += mRefIdSize;
+        mRefs.push_back(refItemId);
+        ALOGV("item id %d: referencing item id %d", mItemId, refItemId);
+    }
+
+    return OK;
+}
+
+struct IrefBox : public FullBox {
+    IrefBox(const sp<DataSource> source, Vector<sp<ItemReference> > *itemRefs) :
+        FullBox(source, FOURCC('i', 'r', 'e', 'f')), mRefIdSize(0), mItemRefs(itemRefs) {}
+
+    status_t parse(off64_t offset, size_t size);
+
+protected:
+    status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
+
+private:
+    uint32_t mRefIdSize;
+    Vector<sp<ItemReference> > *mItemRefs;
+};
+
+status_t IrefBox::parse(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+    status_t err = parseFullBoxHeader(&offset, &size);
+    if (err != OK) {
+        return err;
+    }
+
+    mRefIdSize = (version() == 0) ? 2 : 4;
+    return parseChunks(offset, size);
+}
+
+status_t IrefBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
+    sp<ItemReference> itemRef = new ItemReference(source(), type, mRefIdSize);
+
+    status_t err = itemRef->parse(offset, size);
+    if (err != OK) {
+        return err;
+    }
+    mItemRefs->push_back(itemRef);
+    return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+//  ItemProperty related boxes
+//
+
+struct AssociationEntry {
+    uint32_t itemId;
+    bool essential;
+    uint16_t index;
+};
+
+struct ItemProperty : public RefBase {
+    ItemProperty() {}
+
+    virtual void attachTo(ImageItem &/*image*/) const {
+        ALOGW("Unrecognized property");
+    }
+    virtual status_t parse(off64_t /*offset*/, size_t /*size*/) {
+        ALOGW("Unrecognized property");
+        return OK;
+    }
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(ItemProperty);
+};
+
+struct IspeBox : public FullBox, public ItemProperty {
+    IspeBox(const sp<DataSource> source) :
+        FullBox(source, FOURCC('i', 's', 'p', 'e')), mWidth(0), mHeight(0) {}
+
+    status_t parse(off64_t offset, size_t size) override;
+
+    void attachTo(ImageItem &image) const override {
+        image.width = mWidth;
+        image.height = mHeight;
+    }
+
+private:
+    uint32_t mWidth;
+    uint32_t mHeight;
+};
+
+status_t IspeBox::parse(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    status_t err = parseFullBoxHeader(&offset, &size);
+    if (err != OK) {
+        return err;
+    }
+
+    if (size < 8) {
+        return ERROR_MALFORMED;
+    }
+    if (!source()->getUInt32(offset, &mWidth)
+            || !source()->getUInt32(offset + 4, &mHeight)) {
+        return ERROR_IO;
+    }
+    ALOGV("property ispe: %dx%d", mWidth, mHeight);
+
+    return OK;
+}
+
+struct HvccBox : public Box, public ItemProperty {
+    HvccBox(const sp<DataSource> source) :
+        Box(source, FOURCC('h', 'v', 'c', 'C')) {}
+
+    status_t parse(off64_t offset, size_t size) override;
+
+    void attachTo(ImageItem &image) const override {
+        image.hvcc = mHVCC;
+    }
+
+private:
+    sp<ABuffer> mHVCC;
+};
+
+status_t HvccBox::parse(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    mHVCC = new ABuffer(size);
+
+    if (mHVCC->data() == NULL) {
+        ALOGE("b/28471206");
+        return NO_MEMORY;
+    }
+
+    if (source()->readAt(offset, mHVCC->data(), size) < (ssize_t)size) {
+        return ERROR_IO;
+    }
+
+    ALOGV("property hvcC");
+
+    return OK;
+}
+
+struct IrotBox : public Box, public ItemProperty {
+    IrotBox(const sp<DataSource> source) :
+        Box(source, FOURCC('i', 'r', 'o', 't')), mAngle(0) {}
+
+    status_t parse(off64_t offset, size_t size) override;
+
+    void attachTo(ImageItem &image) const override {
+        image.rotation = mAngle * 90;
+    }
+
+private:
+    uint8_t mAngle;
+};
+
+status_t IrotBox::parse(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    if (size < 1) {
+        return ERROR_MALFORMED;
+    }
+    if (source()->readAt(offset, &mAngle, 1) != 1) {
+        return ERROR_IO;
+    }
+    mAngle &= 0x3;
+    ALOGV("property irot: %d", mAngle);
+
+    return OK;
+}
+
+struct ColrBox : public Box, public ItemProperty {
+    ColrBox(const sp<DataSource> source) :
+        Box(source, FOURCC('c', 'o', 'l', 'r')) {}
+
+    status_t parse(off64_t offset, size_t size) override;
+
+    void attachTo(ImageItem &image) const override {
+        image.icc = mICCData;
+    }
+
+private:
+    sp<ABuffer> mICCData;
+};
+
+status_t ColrBox::parse(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    if (size < 4) {
+        return ERROR_MALFORMED;
+    }
+    uint32_t colour_type;
+    if (!source()->getUInt32(offset, &colour_type)) {
+        return ERROR_IO;
+    }
+    offset += 4;
+    size -= 4;
+    if (colour_type == FOURCC('n', 'c', 'l', 'x')) {
+        return OK;
+    }
+    if ((colour_type != FOURCC('r', 'I', 'C', 'C')) &&
+        (colour_type != FOURCC('p', 'r', 'o', 'f'))) {
+        return ERROR_MALFORMED;
+    }
+
+    mICCData = new ABuffer(size);
+    if (mICCData->data() == NULL) {
+        ALOGE("b/28471206");
+        return NO_MEMORY;
+    }
+
+    if (source()->readAt(offset, mICCData->data(), size) != (ssize_t)size) {
+        return ERROR_IO;
+    }
+
+    ALOGV("property Colr: size %zd", size);
+    return OK;
+}
+
+struct IpmaBox : public FullBox {
+    IpmaBox(const sp<DataSource> source, Vector<AssociationEntry> *associations) :
+        FullBox(source, FOURCC('i', 'p', 'm', 'a')), mAssociations(associations) {}
+
+    status_t parse(off64_t offset, size_t size);
+private:
+    Vector<AssociationEntry> *mAssociations;
+};
+
+status_t IpmaBox::parse(off64_t offset, size_t size) {
+    status_t err = parseFullBoxHeader(&offset, &size);
+    if (err != OK) {
+        return err;
+    }
+
+    if (size < 4) {
+        return ERROR_MALFORMED;
+    }
+    uint32_t entryCount;
+    if (!source()->getUInt32(offset, &entryCount)) {
+        return ERROR_IO;
+    }
+    offset += 4;
+    size -= 4;
+
+    for (size_t k = 0; k < entryCount; ++k) {
+        uint32_t itemId = 0;
+        size_t itemIdSize = (version() < 1) ? 2 : 4;
+
+        if (size < itemIdSize + 1) {
+            return ERROR_MALFORMED;
+        }
+
+        if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) {
+            return ERROR_IO;
+        }
+        offset += itemIdSize;
+        size -= itemIdSize;
+
+        uint8_t associationCount;
+        if (!source()->readAt(offset, &associationCount, 1)) {
+            return ERROR_IO;
+        }
+        offset++;
+        size--;
+
+        for (size_t i = 0; i < associationCount; ++i) {
+            size_t propIndexSize = (flags() & 1) ? 2 : 1;
+            if (size < propIndexSize) {
+                return ERROR_MALFORMED;
+            }
+            uint16_t propIndex;
+            if (!source()->getUInt16Var(offset, &propIndex, propIndexSize)) {
+                return ERROR_IO;
+            }
+            offset += propIndexSize;
+            size -= propIndexSize;
+            uint16_t bitmask = (1 << (8 * propIndexSize - 1));
+            AssociationEntry entry = {
+                    .itemId = itemId,
+                    .essential = !!(propIndex & bitmask),
+                    .index = (uint16_t) (propIndex & ~bitmask)
+            };
+
+            ALOGV("item id %d associated to property %d (essential %d)",
+                    itemId, entry.index, entry.essential);
+
+            mAssociations->push_back(entry);
+        }
+    }
+
+    return OK;
+}
+
+struct IpcoBox : public Box {
+    IpcoBox(const sp<DataSource> source, Vector<sp<ItemProperty> > *properties) :
+        Box(source, FOURCC('i', 'p', 'c', 'o')), mItemProperties(properties) {}
+
+    status_t parse(off64_t offset, size_t size);
+protected:
+    status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
+
+private:
+    Vector<sp<ItemProperty> > *mItemProperties;
+};
+
+status_t IpcoBox::parse(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+    // push dummy as the index is 1-based
+    mItemProperties->push_back(new ItemProperty());
+    return parseChunks(offset, size);
+}
+
+status_t IpcoBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
+    sp<ItemProperty> itemProperty;
+    switch(type) {
+        case FOURCC('h', 'v', 'c', 'C'):
+        {
+            itemProperty = new HvccBox(source());
+            break;
+        }
+        case FOURCC('i', 's', 'p', 'e'):
+        {
+            itemProperty = new IspeBox(source());
+            break;
+        }
+        case FOURCC('i', 'r', 'o', 't'):
+        {
+            itemProperty = new IrotBox(source());
+            break;
+        }
+        case FOURCC('c', 'o', 'l', 'r'):
+        {
+            itemProperty = new ColrBox(source());
+            break;
+        }
+        default:
+        {
+            // push dummy to maintain correct item property index
+            itemProperty = new ItemProperty();
+            break;
+        }
+    }
+    status_t err = itemProperty->parse(offset, size);
+    if (err != OK) {
+        return err;
+    }
+    mItemProperties->push_back(itemProperty);
+    return OK;
+}
+
+struct IprpBox : public Box {
+    IprpBox(const sp<DataSource> source,
+            Vector<sp<ItemProperty> > *properties,
+            Vector<AssociationEntry> *associations) :
+        Box(source, FOURCC('i', 'p', 'r', 'p')),
+        mProperties(properties), mAssociations(associations) {}
+
+    status_t parse(off64_t offset, size_t size);
+protected:
+    status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
+
+private:
+    Vector<sp<ItemProperty> > *mProperties;
+    Vector<AssociationEntry> *mAssociations;
+};
+
+status_t IprpBox::parse(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    status_t err = parseChunks(offset, size);
+    if (err != OK) {
+        return err;
+    }
+    return OK;
+}
+
+status_t IprpBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
+    switch(type) {
+        case FOURCC('i', 'p', 'c', 'o'):
+        {
+            IpcoBox ipcoBox(source(), mProperties);
+            return ipcoBox.parse(offset, size);
+        }
+        case FOURCC('i', 'p', 'm', 'a'):
+        {
+            IpmaBox ipmaBox(source(), mAssociations);
+            return ipmaBox.parse(offset, size);
+        }
+        default:
+        {
+            ALOGW("Unrecognized box.");
+            break;
+        }
+    }
+    return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+//  ItemInfo related boxes
+//
+struct ItemInfo {
+    uint32_t itemId;
+    uint32_t itemType;
+};
+
+struct InfeBox : public FullBox {
+    InfeBox(const sp<DataSource> source) :
+        FullBox(source, FOURCC('i', 'n', 'f', 'e')) {}
+
+    status_t parse(off64_t offset, size_t size, ItemInfo *itemInfo);
+
+private:
+    bool parseNullTerminatedString(off64_t *offset, size_t *size, String8 *out);
+};
+
+bool InfeBox::parseNullTerminatedString(
+        off64_t *offset, size_t *size, String8 *out) {
+    char tmp[256];
+    size_t len = 0;
+    off64_t newOffset = *offset;
+    off64_t stopOffset = *offset + *size;
+    while (newOffset < stopOffset) {
+        if (!source()->readAt(newOffset++, &tmp[len], 1)) {
+            return false;
+        }
+        if (tmp[len] == 0) {
+            out->append(tmp, len);
+
+            *offset = newOffset;
+            *size = stopOffset - newOffset;
+
+            return true;
+        }
+        if (++len >= sizeof(tmp)) {
+            out->append(tmp, len);
+            len = 0;
+        }
+    }
+    return false;
+}
+
+status_t InfeBox::parse(off64_t offset, size_t size, ItemInfo *itemInfo) {
+    status_t err = parseFullBoxHeader(&offset, &size);
+    if (err != OK) {
+        return err;
+    }
+
+    if (version() == 0 || version() == 1) {
+        if (size < 4) {
+            return ERROR_MALFORMED;
+        }
+        uint16_t item_id;
+        if (!source()->getUInt16(offset, &item_id)) {
+            return ERROR_IO;
+        }
+        ALOGV("item_id %d", item_id);
+        uint16_t item_protection_index;
+        if (!source()->getUInt16(offset + 2, &item_protection_index)) {
+            return ERROR_IO;
+        }
+        offset += 4;
+        size -= 4;
+
+        String8 item_name;
+        if (!parseNullTerminatedString(&offset, &size, &item_name)) {
+            return ERROR_MALFORMED;
+        }
+
+        String8 content_type;
+        if (!parseNullTerminatedString(&offset, &size, &content_type)) {
+            return ERROR_MALFORMED;
+        }
+
+        String8 content_encoding;
+        if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
+            return ERROR_MALFORMED;
+        }
+
+        if (version() == 1) {
+            uint32_t extension_type;
+            if (!source()->getUInt32(offset, &extension_type)) {
+                return ERROR_IO;
+            }
+            offset++;
+            size--;
+            // TODO: handle this case
+        }
+    } else { // version >= 2
+        uint32_t item_id;
+        size_t itemIdSize = (version() == 2) ? 2 : 4;
+        if (size < itemIdSize + 6) {
+            return ERROR_MALFORMED;
+        }
+        if (!source()->getUInt32Var(offset, &item_id, itemIdSize)) {
+            return ERROR_IO;
+        }
+        ALOGV("item_id %d", item_id);
+        offset += itemIdSize;
+        uint16_t item_protection_index;
+        if (!source()->getUInt16(offset, &item_protection_index)) {
+            return ERROR_IO;
+        }
+        ALOGV("item_protection_index %d", item_protection_index);
+        offset += 2;
+        uint32_t item_type;
+        if (!source()->getUInt32(offset, &item_type)) {
+            return ERROR_IO;
+        }
+
+        itemInfo->itemId = item_id;
+        itemInfo->itemType = item_type;
+
+        char itemTypeString[5];
+        MakeFourCCString(item_type, itemTypeString);
+        ALOGV("item_type %s", itemTypeString);
+        offset += 4;
+        size -= itemIdSize + 6;
+
+        String8 item_name;
+        if (!parseNullTerminatedString(&offset, &size, &item_name)) {
+            return ERROR_MALFORMED;
+        }
+        ALOGV("item_name %s", item_name.c_str());
+
+        if (item_type == FOURCC('m', 'i', 'm', 'e')) {
+            String8 content_type;
+            if (!parseNullTerminatedString(&offset, &size, &content_type)) {
+                return ERROR_MALFORMED;
+            }
+
+            String8 content_encoding;
+            if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
+                return ERROR_MALFORMED;
+            }
+        } else if (item_type == FOURCC('u', 'r', 'i', ' ')) {
+            String8 item_uri_type;
+            if (!parseNullTerminatedString(&offset, &size, &item_uri_type)) {
+                return ERROR_MALFORMED;
+            }
+        }
+    }
+    return OK;
+}
+
+struct IinfBox : public FullBox {
+    IinfBox(const sp<DataSource> source, Vector<ItemInfo> *itemInfos) :
+        FullBox(source, FOURCC('i', 'i', 'n', 'f')),
+        mItemInfos(itemInfos), mHasGrids(false) {}
+
+    status_t parse(off64_t offset, size_t size);
+
+    bool hasGrids() { return mHasGrids; }
+
+protected:
+    status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
+
+private:
+    Vector<ItemInfo> *mItemInfos;
+    bool mHasGrids;
+};
+
+status_t IinfBox::parse(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    status_t err = parseFullBoxHeader(&offset, &size);
+    if (err != OK) {
+        return err;
+    }
+
+    size_t entryCountSize = version() == 0 ? 2 : 4;
+    if (size < entryCountSize) {
+        return ERROR_MALFORMED;
+    }
+    uint32_t entry_count;
+    if (!source()->getUInt32Var(offset, &entry_count, entryCountSize)) {
+        return ERROR_IO;
+    }
+    ALOGV("entry_count %d", entry_count);
+
+    off64_t stopOffset = offset + size;
+    offset += entryCountSize;
+    for (size_t i = 0; i < entry_count && offset < stopOffset; i++) {
+        ALOGV("entry %zu", i);
+        status_t err = parseChunk(&offset);
+        if (err != OK) {
+            return err;
+        }
+    }
+    if (offset != stopOffset) {
+        return ERROR_MALFORMED;
+    }
+
+    return OK;
+}
+
+status_t IinfBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
+    if (type != FOURCC('i', 'n', 'f', 'e')) {
+        return OK;
+    }
+
+    InfeBox infeBox(source());
+    ItemInfo itemInfo;
+    status_t err = infeBox.parse(offset, size, &itemInfo);
+    if (err != OK) {
+        return err;
+    }
+    mItemInfos->push_back(itemInfo);
+    mHasGrids |= (itemInfo.itemType == FOURCC('g', 'r', 'i', 'd'));
+    return OK;
+}
+
+//////////////////////////////////////////////////////////////////
+
+ItemTable::ItemTable(const sp<DataSource> &source)
+    : mDataSource(source),
+      mPrimaryItemId(0),
+      mIdatOffset(0),
+      mIdatSize(0),
+      mImageItemsValid(false),
+      mCurrentImageIndex(0) {
+    mRequiredBoxes.insert('iprp');
+    mRequiredBoxes.insert('iloc');
+    mRequiredBoxes.insert('pitm');
+    mRequiredBoxes.insert('iinf');
+}
+
+ItemTable::~ItemTable() {}
+
+status_t ItemTable::parse(uint32_t type, off64_t data_offset, size_t chunk_data_size) {
+    switch(type) {
+        case FOURCC('i', 'l', 'o', 'c'):
+        {
+            return parseIlocBox(data_offset, chunk_data_size);
+        }
+        case FOURCC('i', 'i', 'n', 'f'):
+        {
+            return parseIinfBox(data_offset, chunk_data_size);
+        }
+        case FOURCC('i', 'p', 'r', 'p'):
+        {
+            return parseIprpBox(data_offset, chunk_data_size);
+        }
+        case FOURCC('p', 'i', 't', 'm'):
+        {
+            return parsePitmBox(data_offset, chunk_data_size);
+        }
+        case FOURCC('i', 'd', 'a', 't'):
+        {
+            return parseIdatBox(data_offset, chunk_data_size);
+        }
+        case FOURCC('i', 'r', 'e', 'f'):
+        {
+            return parseIrefBox(data_offset, chunk_data_size);
+        }
+        case FOURCC('i', 'p', 'r', 'o'):
+        {
+            ALOGW("ipro box not supported!");
+            break;
+        }
+        default:
+        {
+            ALOGW("unrecognized box type: 0x%x", type);
+            break;
+        }
+    }
+    return ERROR_UNSUPPORTED;
+}
+
+status_t ItemTable::parseIlocBox(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    IlocBox ilocBox(mDataSource, &mItemLocs);
+    status_t err = ilocBox.parse(offset, size);
+    if (err != OK) {
+        return err;
+    }
+
+    if (ilocBox.hasConstructMethod1()) {
+        mRequiredBoxes.insert('idat');
+    }
+
+    return buildImageItemsIfPossible('iloc');
+}
+
+status_t ItemTable::parseIinfBox(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    IinfBox iinfBox(mDataSource, &mItemInfos);
+    status_t err = iinfBox.parse(offset, size);
+    if (err != OK) {
+        return err;
+    }
+
+    if (iinfBox.hasGrids()) {
+        mRequiredBoxes.insert('iref');
+    }
+
+    return buildImageItemsIfPossible('iinf');
+}
+
+status_t ItemTable::parsePitmBox(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    PitmBox pitmBox(mDataSource);
+    status_t err = pitmBox.parse(offset, size, &mPrimaryItemId);
+    if (err != OK) {
+        return err;
+    }
+
+    return buildImageItemsIfPossible('pitm');
+}
+
+status_t ItemTable::parseIprpBox(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    IprpBox iprpBox(mDataSource, &mItemProperties, &mAssociations);
+    status_t err = iprpBox.parse(offset, size);
+    if (err != OK) {
+        return err;
+    }
+
+    return buildImageItemsIfPossible('iprp');
+}
+
+status_t ItemTable::parseIdatBox(off64_t offset, size_t size) {
+    ALOGV("%s: idat offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    // only remember the offset and size of idat box for later use
+    mIdatOffset = offset;
+    mIdatSize = size;
+
+    return buildImageItemsIfPossible('idat');
+}
+
+status_t ItemTable::parseIrefBox(off64_t offset, size_t size) {
+    ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+    IrefBox irefBox(mDataSource, &mItemReferences);
+    status_t err = irefBox.parse(offset, size);
+    if (err != OK) {
+        return err;
+    }
+
+    return buildImageItemsIfPossible('iref');
+}
+
+status_t ItemTable::buildImageItemsIfPossible(uint32_t type) {
+    if (mImageItemsValid) {
+        return OK;
+    }
+
+    mBoxesSeen.insert(type);
+
+    // need at least 'iprp', 'iloc', 'pitm', 'iinf';
+    // need 'idat' if any items used construction_method of 2;
+    // need 'iref' if there are grids.
+    if (!std::includes(
+            mBoxesSeen.begin(), mBoxesSeen.end(),
+            mRequiredBoxes.begin(), mRequiredBoxes.end())) {
+        return OK;
+    }
+
+    ALOGV("building image table...");
+
+    for (size_t i = 0; i < mItemInfos.size(); i++) {
+        const ItemInfo &info = mItemInfos[i];
+
+
+        // ignore non-image items
+        if (info.itemType != FOURCC('g', 'r', 'i', 'd') &&
+            info.itemType != FOURCC('h', 'v', 'c', '1')) {
+            continue;
+        }
+
+        ssize_t imageIndex = mItemIdToImageMap.indexOfKey(info.itemId);
+        if (imageIndex >= 0) {
+            ALOGW("ignoring duplicate image item id %d", info.itemId);
+            continue;
+        }
+
+        ssize_t ilocIndex = mItemLocs.indexOfKey(info.itemId);
+        if (ilocIndex < 0) {
+            ALOGE("iloc missing for image item id %d", info.itemId);
+            continue;
+        }
+        const ItemLoc &iloc = mItemLocs[ilocIndex];
+
+        off64_t offset;
+        size_t size;
+        if (iloc.getLoc(&offset, &size, mIdatOffset, mIdatSize) != OK) {
+            return ERROR_MALFORMED;
+        }
+
+        ImageItem image(info.itemType);
+
+        ALOGV("adding %s: itemId %d", image.isGrid() ? "grid" : "image", info.itemId);
+
+        if (image.isGrid()) {
+            if (size > 12) {
+                return ERROR_MALFORMED;
+            }
+            uint8_t buf[12];
+            if (!mDataSource->readAt(offset, buf, size)) {
+                return ERROR_IO;
+            }
+
+            image.rows = buf[2] + 1;
+            image.columns = buf[3] + 1;
+
+            ALOGV("rows %d, columans %d", image.rows, image.columns);
+        } else {
+            image.offset = offset;
+            image.size = size;
+        }
+        mItemIdToImageMap.add(info.itemId, image);
+    }
+
+    for (size_t i = 0; i < mAssociations.size(); i++) {
+        attachProperty(mAssociations[i]);
+    }
+
+    for (size_t i = 0; i < mItemReferences.size(); i++) {
+        mItemReferences[i]->apply(mItemIdToImageMap);
+    }
+
+    mImageItemsValid = true;
+    return OK;
+}
+
+void ItemTable::attachProperty(const AssociationEntry &association) {
+    ssize_t imageIndex = mItemIdToImageMap.indexOfKey(association.itemId);
+
+    // ignore non-image items
+    if (imageIndex < 0) {
+        return;
+    }
+
+    uint16_t propertyIndex = association.index;
+    if (propertyIndex >= mItemProperties.size()) {
+        ALOGW("Ignoring invalid property index %d", propertyIndex);
+        return;
+    }
+
+    ALOGV("attach property %d to item id %d)",
+            propertyIndex, association.itemId);
+
+    mItemProperties[propertyIndex]->attachTo(
+            mItemIdToImageMap.editValueAt(imageIndex));
+}
+
+sp<MetaData> ItemTable::getImageMeta() {
+    if (!mImageItemsValid) {
+        return NULL;
+    }
+
+    ssize_t imageIndex = mItemIdToImageMap.indexOfKey(mPrimaryItemId);
+    if (imageIndex < 0) {
+        ALOGE("Primary item id %d not found!", mPrimaryItemId);
+        return NULL;
+    }
+
+    ALOGV("primary image index %zu", imageIndex);
+
+    const ImageItem *image = &mItemIdToImageMap[imageIndex];
+
+    sp<MetaData> meta = new MetaData;
+    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+
+    ALOGV("setting image size %dx%d", image->width, image->height);
+    meta->setInt32(kKeyWidth, image->width);
+    meta->setInt32(kKeyHeight, image->height);
+    if (image->rotation != 0) {
+        meta->setInt32(kKeyRotation, image->rotation);
+    }
+    meta->setInt32(kKeyMaxInputSize, image->width * image->height * 1.5);
+
+    if (!image->thumbnails.empty()) {
+        ssize_t thumbnailIndex = mItemIdToImageMap.indexOfKey(image->thumbnails[0]);
+        if (thumbnailIndex >= 0) {
+            const ImageItem &thumbnail = mItemIdToImageMap[thumbnailIndex];
+
+            meta->setInt32(kKeyThumbnailWidth, thumbnail.width);
+            meta->setInt32(kKeyThumbnailHeight, thumbnail.height);
+            meta->setData(kKeyThumbnailHVCC, kTypeHVCC,
+                    thumbnail.hvcc->data(), thumbnail.hvcc->size());
+            ALOGV("thumbnail meta: %dx%d, index %zd",
+                    thumbnail.width, thumbnail.height, thumbnailIndex);
+        } else {
+            ALOGW("Referenced thumbnail does not exist!");
+        }
+    }
+
+    if (image->isGrid()) {
+        ssize_t tileIndex = mItemIdToImageMap.indexOfKey(image->dimgRefs[0]);
+        if (tileIndex < 0) {
+            return NULL;
+        }
+        meta->setInt32(kKeyGridRows, image->rows);
+        meta->setInt32(kKeyGridCols, image->columns);
+
+        image = &mItemIdToImageMap.editValueAt(tileIndex);
+    }
+
+    if (image->hvcc == NULL) {
+        ALOGE("hvcc is missing!");
+        return NULL;
+    }
+    meta->setData(kKeyHVCC, kTypeHVCC, image->hvcc->data(), image->hvcc->size());
+
+    if (image->icc != NULL) {
+        meta->setData(kKeyIccProfile, 0, image->icc->data(), image->icc->size());
+    }
+    return meta;
+}
+
+uint32_t ItemTable::countImages() const {
+    return mImageItemsValid ? mItemIdToImageMap.size() : 0;
+}
+
+status_t ItemTable::findPrimaryImage(uint32_t *imageIndex) {
+    if (!mImageItemsValid) {
+        return INVALID_OPERATION;
+    }
+
+    ssize_t index = mItemIdToImageMap.indexOfKey(mPrimaryItemId);
+    if (index < 0) {
+        return ERROR_MALFORMED;
+    }
+
+    *imageIndex = index;
+    return OK;
+}
+
+status_t ItemTable::findThumbnail(uint32_t *imageIndex) {
+    if (!mImageItemsValid) {
+        return INVALID_OPERATION;
+    }
+
+    ssize_t primaryIndex = mItemIdToImageMap.indexOfKey(mPrimaryItemId);
+    if (primaryIndex < 0) {
+        ALOGE("Primary item id %d not found!", mPrimaryItemId);
+        return ERROR_MALFORMED;
+    }
+
+    const ImageItem &primaryImage = mItemIdToImageMap[primaryIndex];
+    if (primaryImage.thumbnails.empty()) {
+        ALOGW("Using primary in place of thumbnail.");
+        *imageIndex = primaryIndex;
+        return OK;
+    }
+
+    ssize_t thumbnailIndex = mItemIdToImageMap.indexOfKey(
+            primaryImage.thumbnails[0]);
+    if (thumbnailIndex < 0) {
+        ALOGE("Thumbnail item id %d not found!", primaryImage.thumbnails[0]);
+        return ERROR_MALFORMED;
+    }
+
+    *imageIndex = thumbnailIndex;
+    return OK;
+}
+
+status_t ItemTable::getImageOffsetAndSize(
+        uint32_t *imageIndex, off64_t *offset, size_t *size) {
+    if (!mImageItemsValid) {
+        return INVALID_OPERATION;
+    }
+
+    if (imageIndex != NULL) {
+        if (*imageIndex >= mItemIdToImageMap.size()) {
+            ALOGE("Bad image index!");
+            return BAD_VALUE;
+        }
+        mCurrentImageIndex = *imageIndex;
+    }
+
+    ImageItem &image = mItemIdToImageMap.editValueAt(mCurrentImageIndex);
+    if (image.isGrid()) {
+        uint32_t tileItemId;
+        status_t err = image.getNextTileItemId(&tileItemId, imageIndex != NULL);
+        if (err != OK) {
+            return err;
+        }
+        ssize_t tileImageIndex = mItemIdToImageMap.indexOfKey(tileItemId);
+        if (tileImageIndex < 0) {
+            return ERROR_END_OF_STREAM;
+        }
+        *offset = mItemIdToImageMap[tileImageIndex].offset;
+        *size = mItemIdToImageMap[tileImageIndex].size;
+    } else {
+        if (imageIndex == NULL) {
+            // For single images, we only allow it to be read once, after that
+            // it's EOS.  New image index must be requested each time.
+            return ERROR_END_OF_STREAM;
+        }
+        *offset = mItemIdToImageMap[mCurrentImageIndex].offset;
+        *size = mItemIdToImageMap[mCurrentImageIndex].size;
+    }
+
+    return OK;
+}
+
+} // namespace heif
+
+}  // namespace android
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 2e14786..2685f1d 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -28,6 +28,7 @@
 
 #include "include/MPEG4Extractor.h"
 #include "include/SampleTable.h"
+#include "include/ItemTable.h"
 #include "include/ESDS.h"
 
 #include <media/stagefright/foundation/ABitReader.h>
@@ -73,7 +74,8 @@
                 const sp<SampleTable> &sampleTable,
                 Vector<SidxEntry> &sidx,
                 const Trex *trex,
-                off64_t firstMoofOffset);
+                off64_t firstMoofOffset,
+                const sp<ItemTable> &itemTable);
     virtual status_t init();
 
     virtual status_t start(MetaData *params = NULL);
@@ -135,6 +137,9 @@
 
     uint8_t *mSrcBuffer;
 
+    bool mIsHEIF;
+    sp<ItemTable> mItemTable;
+
     size_t parseNALSize(const uint8_t *data) const;
     status_t parseChunk(off64_t *offset);
     status_t parseTrackFragmentHeader(off64_t offset, off64_t size);
@@ -340,6 +345,7 @@
       mInitCheck(NO_INIT),
       mHeaderTimescale(0),
       mIsQT(false),
+      mIsHEIF(false),
       mFirstTrack(NULL),
       mLastTrack(NULL),
       mFileMetaData(new MetaData),
@@ -483,7 +489,8 @@
     status_t err;
     bool sawMoovOrSidx = false;
 
-    while (!(sawMoovOrSidx && (mMdatFound || mMoofFound))) {
+    while (!((sawMoovOrSidx && (mMdatFound || mMoofFound)) ||
+            (mIsHEIF && (mItemTable != NULL) && mItemTable->isValid()))) {
         off64_t orig_offset = offset;
         err = parseChunk(&offset, 0);
 
@@ -535,6 +542,29 @@
         mFileMetaData->setData(kKeyPssh, 'pssh', buf, psshsize);
         free(buf);
     }
+
+    if (mIsHEIF) {
+        sp<MetaData> meta = mItemTable->getImageMeta();
+        if (meta == NULL) {
+            return ERROR_MALFORMED;
+        }
+
+        Track *track = mLastTrack;
+        if (track != NULL) {
+            ALOGW("track is set before metadata is fully processed");
+        } else {
+            track = new Track;
+            track->next = NULL;
+            mFirstTrack = mLastTrack = track;
+        }
+
+        track->meta = meta;
+        track->meta->setInt32(kKeyTrackID, 0);
+        track->includes_expensive_metadata = false;
+        track->skipTrack = false;
+        track->timescale = 0;
+    }
+
     return mInitCheck;
 }
 
@@ -1987,6 +2017,28 @@
             break;
         }
 
+        case FOURCC('i', 'l', 'o', 'c'):
+        case FOURCC('i', 'i', 'n', 'f'):
+        case FOURCC('i', 'p', 'r', 'p'):
+        case FOURCC('p', 'i', 't', 'm'):
+        case FOURCC('i', 'd', 'a', 't'):
+        case FOURCC('i', 'r', 'e', 'f'):
+        case FOURCC('i', 'p', 'r', 'o'):
+        {
+            if (mIsHEIF) {
+                if (mItemTable == NULL) {
+                    mItemTable = new ItemTable(mDataSource);
+                }
+                status_t err = mItemTable->parse(
+                        chunk_type, data_offset, chunk_data_size);
+                if (err != OK) {
+                    return err;
+                }
+            }
+            *offset += chunk_size;
+            break;
+        }
+
         case FOURCC('m', 'e', 'a', 'n'):
         case FOURCC('n', 'a', 'm', 'e'):
         case FOURCC('d', 'a', 't', 'a'):
@@ -2334,6 +2386,7 @@
 
             off64_t stop_offset = *offset + chunk_size;
             uint32_t numCompatibleBrands = (chunk_data_size - 8) / 4;
+            std::set<uint32_t> brandSet;
             for (size_t i = 0; i < numCompatibleBrands + 2; ++i) {
                 if (i == 1) {
                     // Skip this index, it refers to the minorVersion,
@@ -2347,10 +2400,15 @@
                 }
 
                 brand = ntohl(brand);
-                if (brand == FOURCC('q', 't', ' ', ' ')) {
-                    mIsQT = true;
-                    break;
-                }
+                brandSet.insert(brand);
+            }
+
+            if (brandSet.count(FOURCC('q', 't', ' ', ' ')) > 0) {
+                mIsQT = true;
+            } else if (brandSet.count(FOURCC('m', 'i', 'f', '1')) > 0
+                    && brandSet.count(FOURCC('h', 'e', 'i', 'c')) > 0) {
+                mIsHEIF = true;
+                ALOGV("identified HEIF image");
             }
 
             *offset = stop_offset;
@@ -3299,7 +3357,7 @@
 
     sp<MPEG4Source> source =  new MPEG4Source(this,
             track->meta, mDataSource, track->timescale, track->sampleTable,
-            mSidxEntries, trex, mMoofOffset);
+            mSidxEntries, trex, mMoofOffset, mItemTable);
     if (source->init() != OK) {
         return NULL;
     }
@@ -3688,7 +3746,8 @@
         const sp<SampleTable> &sampleTable,
         Vector<SidxEntry> &sidx,
         const Trex *trex,
-        off64_t firstMoofOffset)
+        off64_t firstMoofOffset,
+        const sp<ItemTable> &itemTable)
     : mOwner(owner),
       mFormat(format),
       mDataSource(dataSource),
@@ -3713,7 +3772,9 @@
       mGroup(NULL),
       mBuffer(NULL),
       mWantsNALFragments(false),
-      mSrcBuffer(NULL) {
+      mSrcBuffer(NULL),
+      mIsHEIF(itemTable != NULL),
+      mItemTable(itemTable) {
 
     memset(&mTrackFragmentHeaderInfo, 0, sizeof(mTrackFragmentHeaderInfo));
 
@@ -4488,77 +4549,93 @@
     int64_t seekTimeUs;
     ReadOptions::SeekMode mode;
     if (options && options->getSeekTo(&seekTimeUs, &mode)) {
-        uint32_t findFlags = 0;
-        switch (mode) {
-            case ReadOptions::SEEK_PREVIOUS_SYNC:
-                findFlags = SampleTable::kFlagBefore;
-                break;
-            case ReadOptions::SEEK_NEXT_SYNC:
-                findFlags = SampleTable::kFlagAfter;
-                break;
-            case ReadOptions::SEEK_CLOSEST_SYNC:
-            case ReadOptions::SEEK_CLOSEST:
-                findFlags = SampleTable::kFlagClosest;
-                break;
-            default:
-                CHECK(!"Should not be here.");
-                break;
-        }
+        if (mIsHEIF) {
+            CHECK(mSampleTable == NULL);
+            CHECK(mItemTable != NULL);
 
-        uint32_t sampleIndex;
-        status_t err = mSampleTable->findSampleAtTime(
-                seekTimeUs, 1000000, mTimescale,
-                &sampleIndex, findFlags);
-
-        if (mode == ReadOptions::SEEK_CLOSEST) {
-            // We found the closest sample already, now we want the sync
-            // sample preceding it (or the sample itself of course), even
-            // if the subsequent sync sample is closer.
-            findFlags = SampleTable::kFlagBefore;
-        }
-
-        uint32_t syncSampleIndex;
-        if (err == OK) {
-            err = mSampleTable->findSyncSampleNear(
-                    sampleIndex, &syncSampleIndex, findFlags);
-        }
-
-        uint32_t sampleTime;
-        if (err == OK) {
-            err = mSampleTable->getMetaDataForSample(
-                    sampleIndex, NULL, NULL, &sampleTime);
-        }
-
-        if (err != OK) {
-            if (err == ERROR_OUT_OF_RANGE) {
-                // An attempt to seek past the end of the stream would
-                // normally cause this ERROR_OUT_OF_RANGE error. Propagating
-                // this all the way to the MediaPlayer would cause abnormal
-                // termination. Legacy behaviour appears to be to behave as if
-                // we had seeked to the end of stream, ending normally.
-                err = ERROR_END_OF_STREAM;
+            status_t err;
+            if (seekTimeUs >= 0) {
+                err = mItemTable->findPrimaryImage(&mCurrentSampleIndex);
+            } else {
+                err = mItemTable->findThumbnail(&mCurrentSampleIndex);
             }
-            ALOGV("end of stream");
-            return err;
-        }
+            if (err != OK) {
+                return err;
+            }
+        } else {
+            uint32_t findFlags = 0;
+            switch (mode) {
+                case ReadOptions::SEEK_PREVIOUS_SYNC:
+                    findFlags = SampleTable::kFlagBefore;
+                    break;
+                case ReadOptions::SEEK_NEXT_SYNC:
+                    findFlags = SampleTable::kFlagAfter;
+                    break;
+                case ReadOptions::SEEK_CLOSEST_SYNC:
+                case ReadOptions::SEEK_CLOSEST:
+                    findFlags = SampleTable::kFlagClosest;
+                    break;
+                default:
+                    CHECK(!"Should not be here.");
+                    break;
+            }
 
-        if (mode == ReadOptions::SEEK_CLOSEST) {
-            targetSampleTimeUs = (sampleTime * 1000000ll) / mTimescale;
-        }
+            uint32_t sampleIndex;
+            status_t err = mSampleTable->findSampleAtTime(
+                    seekTimeUs, 1000000, mTimescale,
+                    &sampleIndex, findFlags);
+
+            if (mode == ReadOptions::SEEK_CLOSEST) {
+                // We found the closest sample already, now we want the sync
+                // sample preceding it (or the sample itself of course), even
+                // if the subsequent sync sample is closer.
+                findFlags = SampleTable::kFlagBefore;
+            }
+
+            uint32_t syncSampleIndex;
+            if (err == OK) {
+                err = mSampleTable->findSyncSampleNear(
+                        sampleIndex, &syncSampleIndex, findFlags);
+            }
+
+            uint32_t sampleTime;
+            if (err == OK) {
+                err = mSampleTable->getMetaDataForSample(
+                        sampleIndex, NULL, NULL, &sampleTime);
+            }
+
+            if (err != OK) {
+                if (err == ERROR_OUT_OF_RANGE) {
+                    // An attempt to seek past the end of the stream would
+                    // normally cause this ERROR_OUT_OF_RANGE error. Propagating
+                    // this all the way to the MediaPlayer would cause abnormal
+                    // termination. Legacy behaviour appears to be to behave as if
+                    // we had seeked to the end of stream, ending normally.
+                    err = ERROR_END_OF_STREAM;
+                }
+                ALOGV("end of stream");
+                return err;
+            }
+
+            if (mode == ReadOptions::SEEK_CLOSEST) {
+                targetSampleTimeUs = (sampleTime * 1000000ll) / mTimescale;
+            }
 
 #if 0
-        uint32_t syncSampleTime;
-        CHECK_EQ(OK, mSampleTable->getMetaDataForSample(
-                    syncSampleIndex, NULL, NULL, &syncSampleTime));
+            uint32_t syncSampleTime;
+            CHECK_EQ(OK, mSampleTable->getMetaDataForSample(
+                        syncSampleIndex, NULL, NULL, &syncSampleTime));
 
-        ALOGI("seek to time %lld us => sample at time %lld us, "
-             "sync sample at time %lld us",
-             seekTimeUs,
-             sampleTime * 1000000ll / mTimescale,
-             syncSampleTime * 1000000ll / mTimescale);
+            ALOGI("seek to time %lld us => sample at time %lld us, "
+                 "sync sample at time %lld us",
+                 seekTimeUs,
+                 sampleTime * 1000000ll / mTimescale,
+                 syncSampleTime * 1000000ll / mTimescale);
 #endif
 
-        mCurrentSampleIndex = syncSampleIndex;
+            mCurrentSampleIndex = syncSampleIndex;
+        }
+
         if (mBuffer != NULL) {
             mBuffer->release();
             mBuffer = NULL;
@@ -4575,9 +4652,19 @@
     if (mBuffer == NULL) {
         newBuffer = true;
 
-        status_t err =
-            mSampleTable->getMetaDataForSample(
+        status_t err;
+        if (!mIsHEIF) {
+            err = mSampleTable->getMetaDataForSample(
                     mCurrentSampleIndex, &offset, &size, &cts, &isSyncSample, &stts);
+        } else {
+            err = mItemTable->getImageOffsetAndSize(
+                    options && options->getSeekTo(&seekTimeUs, &mode) ?
+                            &mCurrentSampleIndex : NULL, &offset, &size);
+
+            cts = stts = 0;
+            isSyncSample = 0;
+            ALOGV("image offset %lld, size %zu", (long long)offset, size);
+        }
 
         if (err != OK) {
             return err;
@@ -5147,7 +5234,8 @@
         || !memcmp(header, "ftyp3ge6", 8) || !memcmp(header, "ftyp3gg6", 8)
         || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8)
         || !memcmp(header, "ftypM4A ", 8) || !memcmp(header, "ftypf4v ", 8)
-        || !memcmp(header, "ftypkddi", 8) || !memcmp(header, "ftypM4VP", 8)) {
+        || !memcmp(header, "ftypkddi", 8) || !memcmp(header, "ftypM4VP", 8)
+        || !memcmp(header, "ftypmif1", 8) || !memcmp(header, "ftypheic", 8)) {
         *mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
         *confidence = 0.4;
 
@@ -5176,6 +5264,8 @@
 
         FOURCC('3', 'g', '2', 'a'),  // 3GPP2
         FOURCC('3', 'g', '2', 'b'),
+        FOURCC('m', 'i', 'f', '1'),  // HEIF image
+        FOURCC('h', 'e', 'i', 'c'),  // HEIF image
     };
 
     for (size_t i = 0;
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index 0281563..f36ff97 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -137,19 +137,164 @@
     return OK;
 }
 
+static VideoFrame *allocVideoFrame(
+        const sp<MetaData> &trackMeta, int32_t width, int32_t height, int32_t bpp, bool metaOnly) {
+    int32_t rotationAngle;
+    if (!trackMeta->findInt32(kKeyRotation, &rotationAngle)) {
+        rotationAngle = 0;  // By default, no rotation
+    }
+
+    uint32_t type;
+    const void *iccData;
+    size_t iccSize;
+    if (!trackMeta->findData(kKeyIccProfile, &type, &iccData, &iccSize)){
+        iccData = NULL;
+        iccSize = 0;
+    }
+
+    int32_t sarWidth, sarHeight;
+    int32_t displayWidth, displayHeight;
+    if (trackMeta->findInt32(kKeySARWidth, &sarWidth)
+            && trackMeta->findInt32(kKeySARHeight, &sarHeight)
+            && sarHeight != 0) {
+        displayWidth = (width * sarWidth) / sarHeight;
+        displayHeight = height;
+    } else if (trackMeta->findInt32(kKeyDisplayWidth, &displayWidth)
+                && trackMeta->findInt32(kKeyDisplayHeight, &displayHeight)
+                && displayWidth > 0 && displayHeight > 0
+                && width > 0 && height > 0) {
+        ALOGV("found display size %dx%d", displayWidth, displayHeight);
+    } else {
+        displayWidth = width;
+        displayHeight = height;
+    }
+
+    return new VideoFrame(width, height, displayWidth, displayHeight,
+            rotationAngle, bpp, !metaOnly, iccData, iccSize);
+}
+
+static bool getDstColorFormat(android_pixel_format_t colorFormat,
+        OMX_COLOR_FORMATTYPE *omxColorFormat, int32_t *bpp) {
+    switch (colorFormat) {
+        case HAL_PIXEL_FORMAT_RGB_565:
+        {
+            *omxColorFormat = OMX_COLOR_Format16bitRGB565;
+            *bpp = 2;
+            return true;
+        }
+        case HAL_PIXEL_FORMAT_RGBA_8888:
+        {
+            *omxColorFormat = OMX_COLOR_Format32BitRGBA8888;
+            *bpp = 4;
+            return true;
+        }
+        case HAL_PIXEL_FORMAT_BGRA_8888:
+        {
+            *omxColorFormat = OMX_COLOR_Format32bitBGRA8888;
+            *bpp = 4;
+            return true;
+        }
+        default:
+        {
+            ALOGE("Unsupported color format: %d", colorFormat);
+            break;
+        }
+    }
+    return false;
+}
+
 static VideoFrame *extractVideoFrame(
         const AString &componentName,
         const sp<MetaData> &trackMeta,
         const sp<IMediaSource> &source,
         int64_t frameTimeUs,
         int seekMode,
-        int /*colorFormat*/,
-        bool /*metaOnly*/) {
-
+        int colorFormat,
+        bool metaOnly) {
     sp<MetaData> format = source->getFormat();
 
+    MediaSource::ReadOptions::SeekMode mode =
+            static_cast<MediaSource::ReadOptions::SeekMode>(seekMode);
+    if (seekMode < MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC ||
+        seekMode > MediaSource::ReadOptions::SEEK_CLOSEST) {
+        ALOGE("Unknown seek mode: %d", seekMode);
+        return NULL;
+    }
+
+    int32_t dstBpp;
+    OMX_COLOR_FORMATTYPE dstFormat;
+    if (!getDstColorFormat(
+            (android_pixel_format_t)colorFormat, &dstFormat, &dstBpp)) {
+        return NULL;
+    }
+
+    if (metaOnly) {
+        int32_t width, height;
+        CHECK(trackMeta->findInt32(kKeyWidth, &width));
+        CHECK(trackMeta->findInt32(kKeyHeight, &height));
+        return allocVideoFrame(trackMeta, width, height, dstBpp, true);
+    }
+
+    MediaSource::ReadOptions options;
+    sp<MetaData> overrideMeta;
+    if (frameTimeUs < 0) {
+        uint32_t type;
+        const void *data;
+        size_t size;
+        int64_t thumbNailTime;
+        int32_t thumbnailWidth, thumbnailHeight;
+
+        // if we have a stand-alone thumbnail, set up the override meta,
+        // and set seekTo time to -1.
+        if (trackMeta->findInt32(kKeyThumbnailWidth, &thumbnailWidth)
+         && trackMeta->findInt32(kKeyThumbnailHeight, &thumbnailHeight)
+         && trackMeta->findData(kKeyThumbnailHVCC, &type, &data, &size)){
+            overrideMeta = new MetaData(*trackMeta);
+            overrideMeta->setInt32(kKeyWidth, thumbnailWidth);
+            overrideMeta->setInt32(kKeyHeight, thumbnailHeight);
+            overrideMeta->setData(kKeyHVCC, type, data, size);
+            thumbNailTime = -1ll;
+            ALOGV("thumbnail: %dx%d", thumbnailWidth, thumbnailHeight);
+        } else if (!trackMeta->findInt64(kKeyThumbnailTime, &thumbNailTime)
+                || thumbNailTime < 0) {
+            thumbNailTime = 0;
+        }
+
+        options.setSeekTo(thumbNailTime, mode);
+    } else {
+        options.setSeekTo(frameTimeUs, mode);
+    }
+
+    int32_t gridRows = 1, gridCols = 1;
+    int32_t numTiles = 1, tilesDecoded = 0;
+    if (overrideMeta == NULL) {
+        // check if we're dealing with a tiled heif
+        if (trackMeta->findInt32(kKeyGridRows, &gridRows) && gridRows > 0
+         && trackMeta->findInt32(kKeyGridCols, &gridCols) && gridCols > 0) {
+            int32_t width, height;
+            CHECK(trackMeta->findInt32(kKeyWidth, &width));
+            CHECK(trackMeta->findInt32(kKeyHeight, &height));
+
+            if ((width % gridCols == 0) && (height % gridRows == 0)) {
+                width /= gridCols;
+                height /= gridRows;
+                numTiles = gridCols * gridRows;
+
+                ALOGV("tile: %dx%d, numTiles %d", width, height, numTiles);
+
+                overrideMeta = new MetaData(*trackMeta);
+                overrideMeta->setInt32(kKeyWidth, width);
+                overrideMeta->setInt32(kKeyHeight, height);
+            }
+        }
+        if (overrideMeta == NULL) {
+            gridRows = gridCols = numTiles = 1;
+            overrideMeta = trackMeta;
+        }
+    }
+
     sp<AMessage> videoFormat;
-    if (convertMetaDataToMessage(trackMeta, &videoFormat) != OK) {
+    if (convertMetaDataToMessage(overrideMeta, &videoFormat) != OK) {
         ALOGE("b/23680780");
         ALOGW("Failed to convert meta data to message");
         return NULL;
@@ -162,7 +307,8 @@
     // input and output ports, if seeking to a sync frame. NOTE: This request may
     // fail if component requires more than that for decoding.
     bool isSeekingClosest = (seekMode == MediaSource::ReadOptions::SEEK_CLOSEST);
-    if (!isSeekingClosest) {
+    bool decodeSingleFrame = !isSeekingClosest && (numTiles == 1);
+    if (decodeSingleFrame) {
         videoFormat->setInt32("android._num-input-buffers", 1);
         videoFormat->setInt32("android._num-output-buffers", 1);
     }
@@ -192,30 +338,6 @@
         return NULL;
     }
 
-    MediaSource::ReadOptions options;
-    if (seekMode < MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC ||
-        seekMode > MediaSource::ReadOptions::SEEK_CLOSEST) {
-
-        ALOGE("Unknown seek mode: %d", seekMode);
-        decoder->release();
-        return NULL;
-    }
-
-    MediaSource::ReadOptions::SeekMode mode =
-            static_cast<MediaSource::ReadOptions::SeekMode>(seekMode);
-
-    int64_t thumbNailTime;
-    if (frameTimeUs < 0) {
-        if (!trackMeta->findInt64(kKeyThumbnailTime, &thumbNailTime)
-                || thumbNailTime < 0) {
-            thumbNailTime = 0;
-        }
-        options.setSeekTo(thumbNailTime, mode);
-    } else {
-        thumbNailTime = -1;
-        options.setSeekTo(frameTimeUs, mode);
-    }
-
     err = source->start();
     if (err != OK) {
         ALOGW("source failed to start: %d (%s)", err, asString(err));
@@ -260,6 +382,8 @@
     bool firstSample = true;
     int64_t targetTimeUs = -1ll;
 
+    VideoFrame *frame = NULL;
+
     do {
         size_t inputIndex = -1;
         int64_t ptsUs = 0ll;
@@ -284,6 +408,9 @@
             if (err != OK) {
                 ALOGW("Input Error or EOS");
                 haveMoreInputs = false;
+                if (err == ERROR_END_OF_STREAM) {
+                    err = OK;
+                }
                 break;
             }
             if (firstSample && isSeekingClosest) {
@@ -295,6 +422,7 @@
             if (mediaBuffer->range_length() > codecBuffer->capacity()) {
                 ALOGE("buffer size (%zu) too large for codec input size (%zu)",
                         mediaBuffer->range_length(), codecBuffer->capacity());
+                haveMoreInputs = false;
                 err = BAD_VALUE;
             } else {
                 codecBuffer->setRange(0, mediaBuffer->range_length());
@@ -303,19 +431,20 @@
                 memcpy(codecBuffer->data(),
                         (const uint8_t*)mediaBuffer->data() + mediaBuffer->range_offset(),
                         mediaBuffer->range_length());
-                if (isAvcOrHevc && IsIDR(codecBuffer) && !isSeekingClosest) {
-                    // Only need to decode one IDR frame, unless we're seeking with CLOSEST
-                    // option, in which case we need to actually decode to targetTimeUs.
-                    haveMoreInputs = false;
-                    flags |= MediaCodec::BUFFER_FLAG_EOS;
-                }
             }
 
             mediaBuffer->release();
             break;
         }
 
-        if (err == OK && inputIndex < inputBuffers.size()) {
+        if (haveMoreInputs && inputIndex < inputBuffers.size()) {
+            if (isAvcOrHevc && IsIDR(codecBuffer) && decodeSingleFrame) {
+                // Only need to decode one IDR frame, unless we're seeking with CLOSEST
+                // option, in which case we need to actually decode to targetTimeUs.
+                haveMoreInputs = false;
+                flags |= MediaCodec::BUFFER_FLAG_EOS;
+            }
+
             ALOGV("QueueInput: size=%zu ts=%" PRId64 " us flags=%x",
                     codecBuffer->size(), ptsUs, flags);
             err = decoder->queueInputBuffer(
@@ -354,11 +483,70 @@
                 } else if (err == OK) {
                     // If we're seeking with CLOSEST option and obtained a valid targetTimeUs
                     // from the extractor, decode to the specified frame. Otherwise we're done.
-                    done = (targetTimeUs < 0ll) || (timeUs >= targetTimeUs);
                     ALOGV("Received an output buffer, timeUs=%lld", (long long)timeUs);
-                    if (!done) {
-                        err = decoder->releaseOutputBuffer(index);
+                    sp<MediaCodecBuffer> videoFrameBuffer = outputBuffers.itemAt(index);
+
+                    int32_t width, height;
+                    CHECK(outputFormat != NULL);
+                    CHECK(outputFormat->findInt32("width", &width));
+                    CHECK(outputFormat->findInt32("height", &height));
+
+                    int32_t crop_left, crop_top, crop_right, crop_bottom;
+                    if (!outputFormat->findRect("crop", &crop_left, &crop_top, &crop_right, &crop_bottom)) {
+                        crop_left = crop_top = 0;
+                        crop_right = width - 1;
+                        crop_bottom = height - 1;
                     }
+
+                    if (frame == NULL) {
+                        frame = allocVideoFrame(
+                                overrideMeta,
+                                (crop_right - crop_left + 1) * gridCols,
+                                (crop_bottom - crop_top + 1) * gridRows,
+                                dstBpp,
+                                false /*metaOnly*/);
+                    }
+
+                    int32_t srcFormat;
+                    CHECK(outputFormat->findInt32("color-format", &srcFormat));
+
+                    ColorConverter converter((OMX_COLOR_FORMATTYPE)srcFormat, dstFormat);
+
+                    int32_t dstLeft, dstTop, dstRight, dstBottom;
+                    if (numTiles == 1) {
+                        dstLeft = crop_left;
+                        dstTop = crop_top;
+                        dstRight = crop_right;
+                        dstBottom = crop_bottom;
+                    } else {
+                        dstLeft = tilesDecoded % gridCols * width;
+                        dstTop = tilesDecoded / gridCols * height;
+                        dstRight = dstLeft + width - 1;
+                        dstBottom = dstTop + height - 1;
+                    }
+
+                    if (converter.isValid()) {
+                        err = converter.convert(
+                                (const uint8_t *)videoFrameBuffer->data(),
+                                width, height,
+                                crop_left, crop_top, crop_right, crop_bottom,
+                                frame->mData,
+                                frame->mWidth,
+                                frame->mHeight,
+                                dstLeft, dstTop, dstRight, dstBottom);
+                    } else {
+                        ALOGE("Unable to convert from format 0x%08x to 0x%08x",
+                                srcFormat, dstFormat);
+
+                        err = ERROR_UNSUPPORTED;
+                    }
+
+                    done = (targetTimeUs < 0ll) || (timeUs >= targetTimeUs);
+                    if (numTiles > 1) {
+                        tilesDecoded++;
+                        done &= (tilesDecoded >= numTiles);
+                    }
+                    err = decoder->releaseOutputBuffer(index);
                 } else {
                     ALOGW("Received error %d (%s) instead of output", err, asString(err));
                     done = true;
@@ -368,95 +556,11 @@
         }
     } while (err == OK && !done);
 
-    if (err != OK || size <= 0 || outputFormat == NULL) {
-        ALOGE("Failed to decode thumbnail frame");
-        source->stop();
-        decoder->release();
-        return NULL;
-    }
-
-    ALOGV("successfully decoded video frame.");
-    sp<MediaCodecBuffer> videoFrameBuffer = outputBuffers.itemAt(index);
-
-    if (thumbNailTime >= 0) {
-        if (timeUs != thumbNailTime) {
-            AString mime;
-            CHECK(outputFormat->findString("mime", &mime));
-
-            ALOGV("thumbNailTime = %lld us, timeUs = %lld us, mime = %s",
-                    (long long)thumbNailTime, (long long)timeUs, mime.c_str());
-        }
-    }
-
-    int32_t width, height;
-    CHECK(outputFormat->findInt32("width", &width));
-    CHECK(outputFormat->findInt32("height", &height));
-
-    int32_t crop_left, crop_top, crop_right, crop_bottom;
-    if (!outputFormat->findRect("crop", &crop_left, &crop_top, &crop_right, &crop_bottom)) {
-        crop_left = crop_top = 0;
-        crop_right = width - 1;
-        crop_bottom = height - 1;
-    }
-
-    int32_t rotationAngle;
-    if (!trackMeta->findInt32(kKeyRotation, &rotationAngle)) {
-        rotationAngle = 0;  // By default, no rotation
-    }
-
-    VideoFrame *frame = new VideoFrame;
-    frame->mWidth = crop_right - crop_left + 1;
-    frame->mHeight = crop_bottom - crop_top + 1;
-    frame->mDisplayWidth = frame->mWidth;
-    frame->mDisplayHeight = frame->mHeight;
-    frame->mSize = frame->mWidth * frame->mHeight * 2;
-    frame->mData = new uint8_t[frame->mSize];
-    frame->mRotationAngle = rotationAngle;
-
-    int32_t sarWidth, sarHeight;
-    if (trackMeta->findInt32(kKeySARWidth, &sarWidth)
-            && trackMeta->findInt32(kKeySARHeight, &sarHeight)
-            && sarHeight != 0) {
-        frame->mDisplayWidth = (frame->mDisplayWidth * sarWidth) / sarHeight;
-    } else {
-        int32_t width, height;
-        if (trackMeta->findInt32(kKeyDisplayWidth, &width)
-                && trackMeta->findInt32(kKeyDisplayHeight, &height)
-                && frame->mDisplayWidth > 0 && frame->mDisplayHeight > 0
-                && width > 0 && height > 0) {
-            frame->mDisplayWidth = width;
-            frame->mDisplayHeight = height;
-        }
-    }
-
-    int32_t srcFormat;
-    CHECK(outputFormat->findInt32("color-format", &srcFormat));
-
-    ColorConverter converter((OMX_COLOR_FORMATTYPE)srcFormat, OMX_COLOR_Format16bitRGB565);
-
-    if (converter.isValid()) {
-        err = converter.convert(
-                (const uint8_t *)videoFrameBuffer->data(),
-                width, height,
-                crop_left, crop_top, crop_right, crop_bottom,
-                frame->mData,
-                frame->mWidth,
-                frame->mHeight,
-                0, 0, frame->mWidth - 1, frame->mHeight - 1);
-    } else {
-        ALOGE("Unable to convert from format 0x%08x to RGB565", srcFormat);
-
-        err = ERROR_UNSUPPORTED;
-    }
-
-    videoFrameBuffer.clear();
     source->stop();
-    decoder->releaseOutputBuffer(index);
     decoder->release();
 
     if (err != OK) {
-        ALOGE("Colorconverter failed to convert frame.");
-
+        ALOGE("failed to get video frame (err %d)", err);
         delete frame;
         frame = NULL;
     }
diff --git a/media/libstagefright/include/ItemTable.h b/media/libstagefright/include/ItemTable.h
new file mode 100644
index 0000000..5a6af5e
--- /dev/null
+++ b/media/libstagefright/include/ItemTable.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ITEM_TABLE_H_
+#define ITEM_TABLE_H_
+
+#include <set>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+class DataSource;
+class MetaData;
+
+namespace heif {
+
+struct AssociationEntry;
+struct ImageItem;
+struct ItemLoc;
+struct ItemInfo;
+struct ItemProperty;
+struct ItemReference;
+
+/*
+ * ItemTable keeps track of all image items (including coded images, grids and
+ * tiles) inside a HEIF still image (ISO/IEC FDIS 23008-12.2:2017(E)).
+ */
+
+class ItemTable : public RefBase {
+public:
+    explicit ItemTable(const sp<DataSource> &source);
+
+    status_t parse(uint32_t type, off64_t offset, size_t size);
+
+    bool isValid() { return mImageItemsValid; }
+    sp<MetaData> getImageMeta();
+    uint32_t countImages() const;
+    status_t findPrimaryImage(uint32_t *imageIndex);
+    status_t findThumbnail(uint32_t *thumbnailIndex);
+    status_t getImageOffsetAndSize(
+            uint32_t *imageIndex, off64_t *offset, size_t *size);
+
+protected:
+    ~ItemTable();
+
+private:
+    sp<DataSource> mDataSource;
+
+    KeyedVector<uint32_t, ItemLoc> mItemLocs;
+    Vector<ItemInfo> mItemInfos;
+    Vector<AssociationEntry> mAssociations;
+    Vector<sp<ItemProperty> > mItemProperties;
+    Vector<sp<ItemReference> > mItemReferences;
+
+    uint32_t mPrimaryItemId;
+    off64_t mIdatOffset;
+    size_t mIdatSize;
+
+    std::set<uint32_t> mRequiredBoxes;
+    std::set<uint32_t> mBoxesSeen;
+
+    bool mImageItemsValid;
+    uint32_t mCurrentImageIndex;
+    KeyedVector<uint32_t, ImageItem> mItemIdToImageMap;
+
+    status_t parseIlocBox(off64_t offset, size_t size);
+    status_t parseIinfBox(off64_t offset, size_t size);
+    status_t parsePitmBox(off64_t offset, size_t size);
+    status_t parseIprpBox(off64_t offset, size_t size);
+    status_t parseIdatBox(off64_t offset, size_t size);
+    status_t parseIrefBox(off64_t offset, size_t size);
+
+    void attachProperty(const AssociationEntry &association);
+    status_t buildImageItemsIfPossible(uint32_t type);
+
+    DISALLOW_EVIL_CONSTRUCTORS(ItemTable);
+};
+
+} // namespace heif
+} // namespace android
+
+#endif  // ITEM_TABLE_H_
diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
index f847119..4a4c538 100644
--- a/media/libstagefright/include/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -28,11 +28,14 @@
 #include <utils/String8.h>
 
 namespace android {
-
 struct AMessage;
 class DataSource;
 class SampleTable;
 class String8;
+namespace heif {
+class ItemTable;
+}
+using heif::ItemTable;
 
 struct SidxEntry {
     size_t mSize;
@@ -97,6 +100,7 @@
     status_t mInitCheck;
     uint32_t mHeaderTimescale;
     bool mIsQT;
+    bool mIsHEIF;
 
     Track *mFirstTrack, *mLastTrack;
 
@@ -134,6 +138,8 @@
     SINF *mFirstSINF;
 
     bool mIsDrm;
+    sp<ItemTable> mItemTable;
+
     status_t parseDrmSINF(off64_t *offset, off64_t data_offset);
 
     status_t parseTrackHeader(off64_t data_offset, off64_t data_size);
diff --git a/media/libstagefright/include/media/stagefright/DataSource.h b/media/libstagefright/include/media/stagefright/DataSource.h
index 63eccea..bd863ba 100644
--- a/media/libstagefright/include/media/stagefright/DataSource.h
+++ b/media/libstagefright/include/media/stagefright/DataSource.h
@@ -73,6 +73,11 @@
     bool getUInt32(off64_t offset, uint32_t *x);
     bool getUInt64(off64_t offset, uint64_t *x);
 
+    // read either int<N> or int<2N> into a uint<2N>_t, size is the int size in bytes.
+    bool getUInt16Var(off64_t offset, uint16_t *x, size_t size);
+    bool getUInt32Var(off64_t offset, uint32_t *x, size_t size);
+    bool getUInt64Var(off64_t offset, uint64_t *x, size_t size);
+
     // Reads in "count" entries of type T into vector *x.
     // Returns true if "count" entries can be read.
     // If fewer than "count" entries can be read, return false. In this case,
diff --git a/media/libstagefright/include/media/stagefright/MetaData.h b/media/libstagefright/include/media/stagefright/MetaData.h
index 9676b97..65ad3e4 100644
--- a/media/libstagefright/include/media/stagefright/MetaData.h
+++ b/media/libstagefright/include/media/stagefright/MetaData.h
@@ -38,6 +38,8 @@
     kKeyDisplayHeight     = 'dHgt',  // int32_t, display/presentation
     kKeySARWidth          = 'sarW',  // int32_t, sampleAspectRatio width
     kKeySARHeight         = 'sarH',  // int32_t, sampleAspectRatio height
+    kKeyThumbnailWidth    = 'thbW',  // int32_t, thumbnail width
+    kKeyThumbnailHeight   = 'thbH',  // int32_t, thumbnail height
 
     // a rectangle, if absent assumed to be (0, 0, width - 1, height - 1)
     kKeyCropRect          = 'crop',
@@ -58,6 +60,7 @@
     kKeyAACProfile        = 'aacp',  // int32_t
     kKeyAVCC              = 'avcc',  // raw data
     kKeyHVCC              = 'hvcc',  // raw data
+    kKeyThumbnailHVCC     = 'thvc',  // raw data
     kKeyD263              = 'd263',  // raw data
     kKeyVorbisInfo        = 'vinf',  // raw data
     kKeyVorbisBooks       = 'vboo',  // raw data
@@ -209,6 +212,10 @@
                                    // color Matrix, value defined by ColorAspects.MatrixCoeffs.
     kKeyTemporalLayerId  = 'iLyr', // int32_t, temporal layer-id. 0-based (0 => base layer)
     kKeyTemporalLayerCount = 'cLyr', // int32_t, number of temporal layers encoded
+
+    kKeyGridRows         = 'rows', // int32_t, HEIF grid rows
+    kKeyGridCols         = 'clms', // int32_t, HEIF grid columns
+    kKeyIccProfile       = 'prof', // raw data
 };
 
 enum {