Merge "Parse XMP offset/size for HEIF"
diff --git a/media/extractors/mp4/ItemTable.cpp b/media/extractors/mp4/ItemTable.cpp
index ded3d1a..444664c 100644
--- a/media/extractors/mp4/ItemTable.cpp
+++ b/media/extractors/mp4/ItemTable.cpp
@@ -80,13 +80,15 @@
 
     Vector<uint32_t> thumbnails;
     Vector<uint32_t> dimgRefs;
-    Vector<uint32_t> cdscRefs;
+    Vector<uint32_t> exifRefs;
+    Vector<uint32_t> xmpRefs;
     size_t nextTileIndex;
 };
 
-struct ExifItem {
+struct ExternalMetaItem {
     off64_t offset;
     size_t size;
+    bool isExif;
 };
 
 /////////////////////////////////////////////////////////////////////
@@ -482,7 +484,7 @@
 
     void apply(
             KeyedVector<uint32_t, ImageItem> &itemIdToItemMap,
-            KeyedVector<uint32_t, ExifItem> &itemIdToExifMap) const;
+            KeyedVector<uint32_t, ExternalMetaItem> &itemIdToMetaMap) const;
 
 private:
     uint32_t mItemId;
@@ -494,7 +496,7 @@
 
 void ItemReference::apply(
         KeyedVector<uint32_t, ImageItem> &itemIdToItemMap,
-        KeyedVector<uint32_t, ExifItem> &itemIdToExifMap) const {
+        KeyedVector<uint32_t, ExternalMetaItem> &itemIdToMetaMap) const {
     ALOGV("attach reference type 0x%x to item id %d)", type(), mItemId);
 
     switch(type()) {
@@ -556,15 +558,15 @@
         break;
     }
     case FOURCC("cdsc"): {
-        ssize_t itemIndex = itemIdToExifMap.indexOfKey(mItemId);
+        ssize_t metaIndex = itemIdToMetaMap.indexOfKey(mItemId);
 
-        // ignore non-exif block items
-        if (itemIndex < 0) {
+        // ignore non-meta items
+        if (metaIndex < 0) {
             return;
         }
 
         for (size_t i = 0; i < mRefs.size(); i++) {
-            itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]);
+            ssize_t itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]);
 
             // ignore non-image items
             if (itemIndex < 0) {
@@ -572,7 +574,11 @@
             }
             ALOGV("Image item id %d uses metadata item id %d", mRefs[i], mItemId);
             ImageItem &image = itemIdToItemMap.editValueAt(itemIndex);
-            image.cdscRefs.push_back(mItemId);
+            if (itemIdToMetaMap[metaIndex].isExif) {
+                image.exifRefs.push_back(mItemId);
+            } else {
+                image.xmpRefs.push_back(mItemId);
+            }
         }
         break;
     }
@@ -1065,7 +1071,21 @@
 struct ItemInfo {
     uint32_t itemId;
     uint32_t itemType;
+    String8 contentType;
     bool hidden;
+
+    bool isXmp() const {
+        return itemType == FOURCC("mime") && contentType == String8("application/rdf+xml");
+    }
+    bool isExif() const {
+        return itemType == FOURCC("Exif");
+    }
+    bool isGrid() const {
+        return itemType == FOURCC("grid");
+    }
+    bool isSample() const {
+        return itemType == FOURCC("av01") || itemType == FOURCC("hvc1");
+    }
 };
 
 struct InfeBox : public FullBox {
@@ -1155,6 +1175,7 @@
             if (!parseNullTerminatedString(&offset, &size, &content_type)) {
                 return ERROR_MALFORMED;
             }
+            itemInfo->contentType = content_type;
 
             // content_encoding is optional; can be omitted if would be empty
             if (size > 0) {
@@ -1175,18 +1196,18 @@
 
 struct IinfBox : public FullBox {
     IinfBox(DataSourceHelper *source, Vector<ItemInfo> *itemInfos) :
-        FullBox(source, FOURCC("iinf")), mItemInfos(itemInfos) {}
+        FullBox(source, FOURCC("iinf")), mItemInfos(itemInfos), mNeedIref(false) {}
 
     status_t parse(off64_t offset, size_t size);
 
-    bool hasFourCC(uint32_t type) { return mFourCCSeen.count(type) > 0; }
+    bool needIrefBox() { return mNeedIref; }
 
 protected:
     status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
 
 private:
     Vector<ItemInfo> *mItemInfos;
-    std::unordered_set<uint32_t> mFourCCSeen;
+    bool mNeedIref;
 };
 
 status_t IinfBox::parse(off64_t offset, size_t size) {
@@ -1233,7 +1254,7 @@
     status_t err = infeBox.parse(offset, size, &itemInfo);
     if (err == OK) {
         mItemInfos->push_back(itemInfo);
-        mFourCCSeen.insert(itemInfo.itemType);
+        mNeedIref |= (itemInfo.isExif() || itemInfo.isXmp() || itemInfo.isGrid());
     }
     // InfeBox parse returns ERROR_UNSUPPORTED if the box if an unsupported
     // version. Ignore this error as it's not fatal.
@@ -1323,7 +1344,7 @@
         return err;
     }
 
-    if (iinfBox.hasFourCC(FOURCC("grid")) || iinfBox.hasFourCC(FOURCC("Exif"))) {
+    if (iinfBox.needIrefBox()) {
         mRequiredBoxes.insert('iref');
     }
 
@@ -1399,12 +1420,9 @@
 
         // Only handle 3 types of items, all others are ignored:
         //   'grid': derived image from tiles
-        //   'hvc1': coded image (or tile)
-        //   'Exif': EXIF metadata
-        if (info.itemType != FOURCC("grid") &&
-            info.itemType != FOURCC("hvc1") &&
-            info.itemType != FOURCC("Exif") &&
-            info.itemType != FOURCC("av01")) {
+        //   'hvc1' or 'av01': coded image (or tile)
+        //   'Exif' or XMP: metadata
+        if (!info.isGrid() && !info.isSample() && !info.isExif() && !info.isXmp()) {
             continue;
         }
 
@@ -1427,15 +1445,18 @@
             return ERROR_MALFORMED;
         }
 
-        if (info.itemType == FOURCC("Exif")) {
-            // Only add if the Exif data is non-empty. The first 4 bytes contain
+        if (info.isExif() || info.isXmp()) {
+            // Only add if the meta is non-empty. For Exif, the first 4 bytes contain
             // the offset to TIFF header, which the Exif parser doesn't use.
-            if (size > 4) {
-                ExifItem exifItem = {
+            ALOGV("adding meta to mItemIdToMetaMap: isExif %d, offset %lld, size %lld",
+                    info.isExif(), (long long)offset, (long long)size);
+            if ((info.isExif() && size > 4) || (info.isXmp() && size > 0)) {
+                ExternalMetaItem metaItem = {
+                        .isExif = info.isExif(),
                         .offset = offset,
                         .size = size,
                 };
-                mItemIdToExifMap.add(info.itemId, exifItem);
+                mItemIdToMetaMap.add(info.itemId, metaItem);
             }
             continue;
         }
@@ -1470,7 +1491,7 @@
     }
 
     for (size_t i = 0; i < mItemReferences.size(); i++) {
-        mItemReferences[i]->apply(mItemIdToItemMap, mItemIdToExifMap);
+        mItemReferences[i]->apply(mItemIdToItemMap, mItemIdToMetaMap);
     }
 
     bool foundPrimary = false;
@@ -1747,11 +1768,11 @@
     }
 
     const ImageItem &image = mItemIdToItemMap[itemIndex];
-    if (image.cdscRefs.size() == 0) {
+    if (image.exifRefs.size() == 0) {
         return NAME_NOT_FOUND;
     }
 
-    ssize_t exifIndex = mItemIdToExifMap.indexOfKey(image.cdscRefs[0]);
+    ssize_t exifIndex = mItemIdToMetaMap.indexOfKey(image.exifRefs[0]);
     if (exifIndex < 0) {
         return NAME_NOT_FOUND;
     }
@@ -1759,7 +1780,7 @@
     // skip the first 4-byte of the offset to TIFF header
     uint32_t tiffOffset;
     if (!mDataSource->readAt(
-            mItemIdToExifMap[exifIndex].offset, &tiffOffset, 4)) {
+            mItemIdToMetaMap[exifIndex].offset, &tiffOffset, 4)) {
         return ERROR_IO;
     }
 
@@ -1772,16 +1793,43 @@
     // exif data. The size of the item should be > 4 for a non-empty exif (this
     // was already checked when the item was added). Also check that the tiff
     // header offset is valid.
-    if (mItemIdToExifMap[exifIndex].size <= 4 ||
-            tiffOffset > mItemIdToExifMap[exifIndex].size - 4) {
+    if (mItemIdToMetaMap[exifIndex].size <= 4 ||
+            tiffOffset > mItemIdToMetaMap[exifIndex].size - 4) {
         return ERROR_MALFORMED;
     }
 
     // Offset of 'Exif\0\0' relative to the beginning of 'Exif' item
     // (first 4-byte is the tiff header offset)
     uint32_t exifOffset = 4 + tiffOffset - 6;
-    *offset = mItemIdToExifMap[exifIndex].offset + exifOffset;
-    *size = mItemIdToExifMap[exifIndex].size - exifOffset;
+    *offset = mItemIdToMetaMap[exifIndex].offset + exifOffset;
+    *size = mItemIdToMetaMap[exifIndex].size - exifOffset;
+    return OK;
+}
+
+status_t ItemTable::getXmpOffsetAndSize(off64_t *offset, size_t *size) {
+    if (!mImageItemsValid) {
+        return INVALID_OPERATION;
+    }
+
+    ssize_t itemIndex = mItemIdToItemMap.indexOfKey(mPrimaryItemId);
+
+    // this should not happen, something's seriously wrong.
+    if (itemIndex < 0) {
+        return INVALID_OPERATION;
+    }
+
+    const ImageItem &image = mItemIdToItemMap[itemIndex];
+    if (image.xmpRefs.size() == 0) {
+        return NAME_NOT_FOUND;
+    }
+
+    ssize_t xmpIndex = mItemIdToMetaMap.indexOfKey(image.xmpRefs[0]);
+    if (xmpIndex < 0) {
+        return NAME_NOT_FOUND;
+    }
+
+    *offset = mItemIdToMetaMap[xmpIndex].offset;
+    *size = mItemIdToMetaMap[xmpIndex].size;
     return OK;
 }
 
diff --git a/media/extractors/mp4/ItemTable.h b/media/extractors/mp4/ItemTable.h
index b19dc18..62826b6 100644
--- a/media/extractors/mp4/ItemTable.h
+++ b/media/extractors/mp4/ItemTable.h
@@ -34,7 +34,7 @@
 
 struct AssociationEntry;
 struct ImageItem;
-struct ExifItem;
+struct ExternalMetaItem;
 struct ItemLoc;
 struct ItemInfo;
 struct ItemProperty;
@@ -59,6 +59,7 @@
     status_t getImageOffsetAndSize(
             uint32_t *itemIndex, off64_t *offset, size_t *size);
     status_t getExifOffsetAndSize(off64_t *offset, size_t *size);
+    status_t getXmpOffsetAndSize(off64_t *offset, size_t *size);
 
 protected:
     ~ItemTable();
@@ -84,7 +85,7 @@
     bool mImageItemsValid;
     uint32_t mCurrentItemIndex;
     KeyedVector<uint32_t, ImageItem> mItemIdToItemMap;
-    KeyedVector<uint32_t, ExifItem> mItemIdToExifMap;
+    KeyedVector<uint32_t, ExternalMetaItem> mItemIdToMetaMap;
     Vector<uint32_t> mDisplayables;
 
     status_t parseIlocBox(off64_t offset, size_t size);
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
index 7989d4b..221bf4f 100644
--- a/media/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -681,6 +681,19 @@
             AMediaFormat_setInt64(mFileMetaData,
                     AMEDIAFORMAT_KEY_EXIF_SIZE, (int64_t)exifSize);
         }
+        off64_t xmpOffset;
+        size_t xmpSize;
+        if (mItemTable->getXmpOffsetAndSize(&xmpOffset, &xmpSize) == OK) {
+            // TODO(chz): b/175717339
+            // Use a hard-coded string here instead of named keys. The keys are available
+            // only on API 31+. The mp4 extractor is part of mainline and has min_sdk_version
+            // of 29. This hard-coded string can be replaced with the named constant once
+            // the mp4 extractor is built against API 31+.
+            AMediaFormat_setInt64(mFileMetaData,
+                    "xmp-offset" /*AMEDIAFORMAT_KEY_XMP_OFFSET*/, (int64_t)xmpOffset);
+            AMediaFormat_setInt64(mFileMetaData,
+                    "xmp-size" /*AMEDIAFORMAT_KEY_XMP_SIZE*/, (int64_t)xmpSize);
+        }
         for (uint32_t imageIndex = 0;
                 imageIndex < mItemTable->countImages(); imageIndex++) {
             AMediaFormat *meta = mItemTable->getImageMeta(imageIndex);
diff --git a/media/libmedia/include/media/mediametadataretriever.h b/media/libmedia/include/media/mediametadataretriever.h
index 1fe6ffc..fba1a30 100644
--- a/media/libmedia/include/media/mediametadataretriever.h
+++ b/media/libmedia/include/media/mediametadataretriever.h
@@ -74,6 +74,8 @@
     METADATA_KEY_SAMPLERATE      = 38,
     METADATA_KEY_BITS_PER_SAMPLE = 39,
     METADATA_KEY_VIDEO_CODEC_MIME_TYPE = 40,
+    METADATA_KEY_XMP_OFFSET      = 41,
+    METADATA_KEY_XMP_LENGTH      = 42,
 
     // Add more here...
 };
diff --git a/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp
index 02fb6bb..93e03ee 100644
--- a/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp
+++ b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp
@@ -529,6 +529,15 @@
         mMetaData.add(METADATA_KEY_EXIF_LENGTH, String8(tmp));
     }
 
+    int64_t xmpOffset, xmpSize;
+    if (meta->findInt64(kKeyXmpOffset, &xmpOffset)
+     && meta->findInt64(kKeyXmpSize, &xmpSize)) {
+        sprintf(tmp, "%lld", (long long)xmpOffset);
+        mMetaData.add(METADATA_KEY_XMP_OFFSET, String8(tmp));
+        sprintf(tmp, "%lld", (long long)xmpSize);
+        mMetaData.add(METADATA_KEY_XMP_LENGTH, String8(tmp));
+    }
+
     bool hasAudio = false;
     bool hasVideo = false;
     int32_t videoWidth = -1;
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index fbaa38e..f63740e 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -729,6 +729,8 @@
     {
         { "exif-offset", kKeyExifOffset },
         { "exif-size", kKeyExifSize },
+        { "xmp-offset", kKeyXmpOffset },
+        { "xmp-size", kKeyXmpSize },
         { "target-time", kKeyTargetTime },
         { "thumbnail-time", kKeyThumbnailTime },
         { "timeUs", kKeyTime },
diff --git a/media/libstagefright/include/media/stagefright/MetaDataBase.h b/media/libstagefright/include/media/stagefright/MetaDataBase.h
index f260510..940bd86 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataBase.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataBase.h
@@ -225,6 +225,8 @@
     kKeyExifSize         = 'exsz', // int64_t, Exif data size
     kKeyExifTiffOffset   = 'thdr', // int32_t, if > 0, buffer contains exif data block with
                                    // tiff hdr at specified offset
+    kKeyXmpOffset        = 'xmof', // int64_t, XMP data offset
+    kKeyXmpSize          = 'xmsz', // int64_t, XMP data size
     kKeyPcmBigEndian     = 'pcmb', // bool (int32_t)
 
     // Key for ALAC Magic Cookie
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index 47214c5..8e673ca 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -384,6 +384,8 @@
 EXPORT const char* AMEDIAFORMAT_KEY_TRACK_INDEX = "track-index";
 EXPORT const char* AMEDIAFORMAT_KEY_VALID_SAMPLES = "valid-samples";
 EXPORT const char* AMEDIAFORMAT_KEY_WIDTH = "width";
+EXPORT const char* AMEDIAFORMAT_KEY_XMP_OFFSET = "xmp-offset";
+EXPORT const char* AMEDIAFORMAT_KEY_XMP_SIZE = "xmp-size";
 EXPORT const char* AMEDIAFORMAT_KEY_YEAR = "year";
 
 } // extern "C"
diff --git a/media/ndk/include/media/NdkMediaFormat.h b/media/ndk/include/media/NdkMediaFormat.h
index 8f39929..0b9024f 100644
--- a/media/ndk/include/media/NdkMediaFormat.h
+++ b/media/ndk/include/media/NdkMediaFormat.h
@@ -325,6 +325,8 @@
 #if __ANDROID_API__ >= 31
 extern const char* AMEDIAFORMAT_KEY_SLOW_MOTION_MARKERS __INTRODUCED_IN(31);
 extern const char* AMEDIAFORMAT_KEY_THUMBNAIL_CSD_AV1C __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_KEY_XMP_OFFSET __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_KEY_XMP_SIZE __INTRODUCED_IN(31);
 #endif /* __ANDROID_API__ >= 31 */
 
 __END_DECLS
diff --git a/media/ndk/libmediandk.map.txt b/media/ndk/libmediandk.map.txt
index 96f1710..237b66e 100644
--- a/media/ndk/libmediandk.map.txt
+++ b/media/ndk/libmediandk.map.txt
@@ -151,6 +151,8 @@
     AMEDIAFORMAT_KEY_TRACK_ID; # var introduced=28
     AMEDIAFORMAT_KEY_VALID_SAMPLES; # var introduced=29
     AMEDIAFORMAT_KEY_WIDTH; # var introduced=21
+    AMEDIAFORMAT_KEY_XMP_OFFSET; # var introduced=31
+    AMEDIAFORMAT_KEY_XMP_SIZE; # var introduced=31
     AMEDIAFORMAT_KEY_YEAR; # var introduced=29
     AMediaCodecActionCode_isRecoverable; # introduced=28
     AMediaCodecActionCode_isTransient; # introduced=28