From 6108177c3c41f3f0974095bca643918bb1e24126 Mon Sep 17 00:00:00 2001
From: Max Loh <mloh@google.com>
Date: Mon, 8 Apr 2024 11:31:55 -0700
Subject: [PATCH] aslgen od to hr initial implementation

Bug: 329902686
Test: Unit tests.
Change-Id: I11513892b6f87d1a6fa229c56089152fc6158af1
---
 .../java/com/android/asllib/AslConverter.java |   3 +-
 .../marshallable/AndroidSafetyLabel.java      |   6 +
 .../AndroidSafetyLabelFactory.java            |   7 +
 .../android/asllib/marshallable/AppInfo.java  |   6 +
 .../asllib/marshallable/AppInfoFactory.java   |   6 +
 .../asllib/marshallable/AslMarshallable.java  |   5 +-
 .../marshallable/AslMarshallableFactory.java  |   5 +-
 .../asllib/marshallable/DataCategory.java     |   6 +
 .../marshallable/DataCategoryFactory.java     |  27 +++
 .../asllib/marshallable/DataLabels.java       |  48 ++++
 .../marshallable/DataLabelsFactory.java       | 112 ++++++---
 .../android/asllib/marshallable/DataType.java |   6 +
 .../asllib/marshallable/DataTypeFactory.java  |  27 +++
 .../asllib/marshallable/DeveloperInfo.java    |   6 +
 .../marshallable/DeveloperInfoFactory.java    |   6 +
 .../asllib/marshallable/SafetyLabels.java     |   6 +
 .../marshallable/SafetyLabelsFactory.java     |   6 +
 .../asllib/marshallable/SecurityLabels.java   |   6 +
 .../marshallable/SecurityLabelsFactory.java   |   7 +
 .../marshallable/SystemAppSafetyLabel.java    |   6 +
 .../SystemAppSafetyLabelFactory.java          |   7 +
 .../marshallable/ThirdPartyVerification.java  |   6 +
 .../ThirdPartyVerificationFactory.java        |   7 +
 .../asllib/marshallable/TransparencyInfo.java |   6 +
 .../marshallable/TransparencyInfoFactory.java |   7 +
 .../com/android/asllib/util/XmlUtils.java     | 141 ++++++++---
 .../asllib/marshallable/DataLabelsTest.java   | 225 +++++++++++++++++-
 .../android/asllib/testutils/TestUtils.java   |  92 +++++--
 .../hr/data-category-actions-in-app.xml       |  17 ++
 .../hr/data-category-app-performance.xml      |  11 +
 .../datalabels/hr/data-category-audio.xml     |  11 +
 .../datalabels/hr/data-category-calendar.xml  |   5 +
 .../datalabels/hr/data-category-contacts.xml  |   5 +
 .../hr/data-category-email-text-message.xml   |  11 +
 .../datalabels/hr/data-category-financial.xml |  14 ++
 .../hr/data-category-health-fitness.xml       |   8 +
 .../hr/data-category-identifiers.xml          |   5 +
 .../datalabels/hr/data-category-location.xml  |   8 +
 .../data-category-personal-empty-purpose.xml  |   5 +
 ...data-category-personal-missing-purpose.xml |   4 +
 .../hr/data-category-personal-partial.xml     |   8 +
 ...ta-category-personal-unrecognized-type.xml |   5 +
 .../datalabels/hr/data-category-personal.xml  |  31 +++
 .../hr/data-category-photo-video.xml          |   8 +
 .../hr/data-category-search-and-browsing.xml  |   5 +
 .../datalabels/hr/data-category-storage.xml   |   5 +
 .../hr/data-category-unrecognized-type.xml    |   5 +
 .../hr/data-category-unrecognized.xml         |   5 +
 .../data-labels-accessed-collected-shared.xml |  11 +
 .../od/data-category-actions-in-app.xml       |  31 +++
 .../od/data-category-app-performance.xml      |  21 ++
 .../datalabels/od/data-category-audio.xml     |  21 ++
 .../datalabels/od/data-category-calendar.xml  |  11 +
 .../datalabels/od/data-category-contacts.xml  |  11 +
 .../od/data-category-email-text-message.xml   |  21 ++
 .../datalabels/od/data-category-financial.xml |  26 ++
 .../od/data-category-health-fitness.xml       |  16 ++
 .../od/data-category-identifiers.xml          |  11 +
 .../datalabels/od/data-category-location.xml  |  16 ++
 .../data-category-personal-empty-purpose.xml  |  11 +
 ...data-category-personal-missing-purpose.xml |   8 +
 .../od/data-category-personal-partial.xml     |  17 ++
 .../datalabels/od/data-category-personal.xml  |  54 +++++
 .../od/data-category-photo-video.xml          |  16 ++
 .../od/data-category-search-and-browsing.xml  |  11 +
 .../datalabels/od/data-category-storage.xml   |  11 +
 .../od/data-category-unrecognized-type.xml    |  11 +
 .../od/data-category-unrecognized.xml         |  11 +
 .../data-labels-accessed-collected-shared.xml |  29 +++
 .../od/data-labels-collected-invalid-bool.xml |  13 +
 .../od/data-labels-shared-valid-bool.xml      |   2 +-
 71 files changed, 1271 insertions(+), 93 deletions(-)
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml
 create mode 100644 tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml

diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
index c1c520e99cac..b98161dd26fb 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
@@ -59,7 +59,8 @@ public class AslConverter {
         switch (format) {
             case HUMAN_READABLE:
                 Element appMetadataBundles =
-                        XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
+                        XmlUtils.getSingleChildElement(
+                                document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES, true);
 
                 return new AndroidSafetyLabelFactory()
                         .createFromHrElements(XmlUtils.listOf(appMetadataBundles));
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
index 112b92c9aebb..ecfad91a378f 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
@@ -61,4 +61,10 @@ public class AndroidSafetyLabel implements AslMarshallable {
         }
         return XmlUtils.listOf(aslEle);
     }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
index b69c30f7f522..41ce6e55e989 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
@@ -55,4 +55,11 @@ public class AndroidSafetyLabelFactory implements AslMarshallableFactory<Android
         return new AndroidSafetyLabel(
                 version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public AndroidSafetyLabel createFromOdElements(List<Element> elements)
+            throws MalformedXmlException {
+        return null;
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
index 3f1ddebefe99..21f328d8e2d0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
@@ -142,4 +142,10 @@ public class AppInfo implements AslMarshallable {
         }
         return XmlUtils.listOf(appInfoEle);
     }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
index 59a437d7ece5..6fcf637fe069 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
@@ -72,4 +72,10 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
                 email,
                 website);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public AppInfo createFromOdElements(List<Element> elements) throws MalformedXmlException {
+        return null;
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
index 48747ccbcff6..0a70e7d0d74b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
@@ -23,6 +23,9 @@ import java.util.List;
 
 public interface AslMarshallable {
 
-    /** Creates the on-device DOM element from the AslMarshallable Java Object. */
+    /** Creates the on-device DOM elements from the AslMarshallable Java Object. */
     List<Element> toOdDomElements(Document doc);
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    List<Element> toHrDomElements(Document doc);
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
index a49b3e77155b..39582900f3a0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
@@ -24,6 +24,9 @@ import java.util.List;
 
 public interface AslMarshallableFactory<T extends AslMarshallable> {
 
-    /** Creates an {@link AslMarshallableFactory} from human-readable DOM element */
+    /** Creates an {@link AslMarshallableFactory} from human-readable DOM elements */
     T createFromHrElements(List<Element> elements) throws MalformedXmlException;
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    T createFromOdElements(List<Element> elements) throws MalformedXmlException;
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
index 4d67162b442d..eb975540ce70 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
@@ -59,4 +59,10 @@ public class DataCategory implements AslMarshallable {
         }
         return XmlUtils.listOf(dataCategoryEle);
     }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
index 37d99e7ef85e..90424fe00504 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
@@ -50,4 +50,31 @@ public class DataCategoryFactory implements AslMarshallableFactory<DataCategory>
 
         return new DataCategory(categoryName, dataTypeMap);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public DataCategory createFromOdElements(List<Element> elements) throws MalformedXmlException {
+        Element dataCategoryEle = XmlUtils.getSingleElement(elements);
+        Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>();
+        String categoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME);
+        var odDataTypes = XmlUtils.asElementList(dataCategoryEle.getChildNodes());
+        for (Element odDataTypeEle : odDataTypes) {
+            String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME);
+            if (!DataTypeConstants.getValidDataTypes().containsKey(categoryName)) {
+                throw new MalformedXmlException(
+                        String.format("Unrecognized data category %s", categoryName));
+            }
+            if (!DataTypeConstants.getValidDataTypes().get(categoryName).contains(dataTypeName)) {
+                throw new MalformedXmlException(
+                        String.format(
+                                "Unrecognized data type name %s for category %s",
+                                dataTypeName, categoryName));
+            }
+            dataTypeMap.put(
+                    dataTypeName,
+                    new DataTypeFactory().createFromOdElements(XmlUtils.listOf(odDataTypeEle)));
+        }
+
+        return new DataCategory(categoryName, dataTypeMap);
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
index 7516faf9f77a..4a0d75977d78 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
@@ -79,6 +79,16 @@ public class DataLabels implements AslMarshallable {
         return XmlUtils.listOf(dataLabelsEle);
     }
 
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        Element dataLabelsEle = doc.createElement(XmlUtils.HR_TAG_DATA_LABELS);
+        maybeAppendHrDataUsages(doc, dataLabelsEle, mDataAccessed, XmlUtils.HR_TAG_DATA_ACCESSED);
+        maybeAppendHrDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED);
+        maybeAppendHrDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.HR_TAG_DATA_SHARED);
+        return XmlUtils.listOf(dataLabelsEle);
+    }
+
     private void maybeAppendDataUsages(
             Document doc,
             Element dataLabelsEle,
@@ -100,4 +110,42 @@ public class DataLabels implements AslMarshallable {
         }
         dataLabelsEle.appendChild(dataUsageEle);
     }
+
+    private void maybeAppendHrDataUsages(
+            Document doc,
+            Element dataLabelsEle,
+            Map<String, DataCategory> dataCategoriesMap,
+            String dataUsageTypeName) {
+        if (dataCategoriesMap.isEmpty()) {
+            return;
+        }
+        for (String dataCategoryName : dataCategoriesMap.keySet()) {
+            DataCategory dataCategory = dataCategoriesMap.get(dataCategoryName);
+            for (String dataTypeName : dataCategory.getDataTypes().keySet()) {
+                DataType dataType = dataCategory.getDataTypes().get(dataTypeName);
+                // XmlUtils.appendChildren(dataLabelsEle, dataType.toHrDomElements(doc));
+                Element hrDataTypeEle = doc.createElement(dataUsageTypeName);
+                hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY, dataCategoryName);
+                hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_TYPE, dataTypeName);
+                XmlUtils.maybeSetHrBoolAttr(
+                        hrDataTypeEle,
+                        XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL,
+                        dataType.getIsCollectionOptional());
+                XmlUtils.maybeSetHrBoolAttr(
+                        hrDataTypeEle,
+                        XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL,
+                        dataType.getIsSharingOptional());
+                XmlUtils.maybeSetHrBoolAttr(
+                        hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, dataType.getEphemeral());
+                hrDataTypeEle.setAttribute(
+                        XmlUtils.HR_ATTR_PURPOSES,
+                        String.join(
+                                "|",
+                                dataType.getPurposes().stream()
+                                        .map(DataType.Purpose::toString)
+                                        .toList()));
+                dataLabelsEle.appendChild(hrDataTypeEle);
+            }
+        }
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
index dc77fd08aa53..5473e010cc65 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
@@ -22,7 +22,6 @@ import com.android.asllib.util.MalformedXmlException;
 import com.android.asllib.util.XmlUtils;
 
 import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
 
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -46,9 +45,82 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
                 getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED);
         Map<String, DataCategory> dataShared =
                 getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED);
+        DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared);
+        validateIsXOptional(dataLabels);
+        return dataLabels;
+    }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public DataLabels createFromOdElements(List<Element> elements) throws MalformedXmlException {
+        Element dataLabelsEle = XmlUtils.getSingleElement(elements);
+        if (dataLabelsEle == null) {
+            AslgenUtil.logI("Found no DataLabels in od format.");
+            return null;
+        }
+        Map<String, DataCategory> dataAccessed =
+                getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_ACCESSED);
+        Map<String, DataCategory> dataCollected =
+                getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_COLLECTED);
+        Map<String, DataCategory> dataShared =
+                getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_SHARED);
+        DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared);
+        validateIsXOptional(dataLabels);
+        return dataLabels;
+    }
+
+    private static Map<String, DataCategory> getOdDataCategoriesWithTag(
+            Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException {
+        Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>();
+        Element dataUsageEle =
+                XmlUtils.getOdPbundleWithName(dataLabelsEle, dataCategoryUsageTypeTag, false);
+        if (dataUsageEle == null) {
+            return dataCategoryMap;
+        }
+        List<Element> dataCategoryEles = XmlUtils.asElementList(dataUsageEle.getChildNodes());
+        for (Element dataCategoryEle : dataCategoryEles) {
+            String dataCategoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME);
+            DataCategory dataCategory =
+                    new DataCategoryFactory().createFromOdElements(List.of(dataCategoryEle));
+            dataCategoryMap.put(dataCategoryName, dataCategory);
+        }
+        return dataCategoryMap;
+    }
 
+    private static Map<String, DataCategory> getDataCategoriesWithTag(
+            Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException {
+        List<Element> dataUsedElements =
+                XmlUtils.getChildrenByTagName(dataLabelsEle, dataCategoryUsageTypeTag);
+        Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>();
+
+        Set<String> dataCategoryNames = new HashSet<String>();
+        for (int i = 0; i < dataUsedElements.size(); i++) {
+            Element dataUsedEle = dataUsedElements.get(i);
+            String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
+            if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) {
+                throw new MalformedXmlException(
+                        String.format("Unrecognized category name: %s", dataCategoryName));
+            }
+            dataCategoryNames.add(dataCategoryName);
+        }
+        for (String dataCategoryName : dataCategoryNames) {
+            var dataCategoryElements =
+                    dataUsedElements.stream()
+                            .filter(
+                                    ele ->
+                                            ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY)
+                                                    .equals(dataCategoryName))
+                            .toList();
+            DataCategory dataCategory =
+                    new DataCategoryFactory().createFromHrElements(dataCategoryElements);
+            dataCategoryMap.put(dataCategoryName, dataCategory);
+        }
+        return dataCategoryMap;
+    }
+
+    private void validateIsXOptional(DataLabels dataLabels) throws MalformedXmlException {
         // Validate booleans such as isCollectionOptional, isSharingOptional.
-        for (DataCategory dataCategory : dataAccessed.values()) {
+        for (DataCategory dataCategory : dataLabels.getDataAccessed().values()) {
             for (DataType dataType : dataCategory.getDataTypes().values()) {
                 if (dataType.getIsSharingOptional() != null) {
                     throw new MalformedXmlException(
@@ -66,7 +138,7 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
                 }
             }
         }
-        for (DataCategory dataCategory : dataCollected.values()) {
+        for (DataCategory dataCategory : dataLabels.getDataCollected().values()) {
             for (DataType dataType : dataCategory.getDataTypes().values()) {
                 if (dataType.getIsSharingOptional() != null) {
                     throw new MalformedXmlException(
@@ -77,7 +149,7 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
                 }
             }
         }
-        for (DataCategory dataCategory : dataShared.values()) {
+        for (DataCategory dataCategory : dataLabels.getDataShared().values()) {
             for (DataType dataType : dataCategory.getDataTypes().values()) {
                 if (dataType.getIsCollectionOptional() != null) {
                     throw new MalformedXmlException(
@@ -88,37 +160,5 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
                 }
             }
         }
-
-        return new DataLabels(dataAccessed, dataCollected, dataShared);
-    }
-
-    private static Map<String, DataCategory> getDataCategoriesWithTag(
-            Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException {
-        NodeList dataUsedNodeList = dataLabelsEle.getElementsByTagName(dataCategoryUsageTypeTag);
-        Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>();
-
-        Set<String> dataCategoryNames = new HashSet<String>();
-        for (int i = 0; i < dataUsedNodeList.getLength(); i++) {
-            Element dataUsedEle = (Element) dataUsedNodeList.item(i);
-            String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
-            if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) {
-                throw new MalformedXmlException(
-                        String.format("Unrecognized category name: %s", dataCategoryName));
-            }
-            dataCategoryNames.add(dataCategoryName);
-        }
-        for (String dataCategoryName : dataCategoryNames) {
-            var dataCategoryElements =
-                    XmlUtils.asElementList(dataUsedNodeList).stream()
-                            .filter(
-                                    ele ->
-                                            ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY)
-                                                    .equals(dataCategoryName))
-                            .toList();
-            DataCategory dataCategory =
-                    new DataCategoryFactory().createFromHrElements(dataCategoryElements);
-            dataCategoryMap.put(dataCategoryName, dataCategory);
-        }
-        return dataCategoryMap;
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
index 347136237966..02b7189c09ba 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
@@ -160,6 +160,12 @@ public class DataType implements AslMarshallable {
         return XmlUtils.listOf(dataTypeEle);
     }
 
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
+
     private static void maybeAddBoolToOdElement(
             Document doc, Element parentEle, Boolean b, String odName) {
         if (b == null) {
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
index ed434cda0823..488c2595912a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
@@ -51,4 +51,31 @@ public class DataTypeFactory implements AslMarshallableFactory<DataType> {
         return new DataType(
                 dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public DataType createFromOdElements(List<Element> elements) throws MalformedXmlException {
+        Element odDataTypeEle = XmlUtils.getSingleElement(elements);
+        String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME);
+        List<Integer> purposeInts =
+                XmlUtils.getOdIntArray(odDataTypeEle, XmlUtils.OD_NAME_PURPOSES, true);
+        List<DataType.Purpose> purposes =
+                purposeInts.stream().map(DataType.Purpose::forValue).collect(Collectors.toList());
+        if (purposes.isEmpty()) {
+            throw new MalformedXmlException(String.format("Found no purpose in: %s", dataTypeName));
+        }
+        if (new HashSet<>(purposes).size() != purposes.size()) {
+            throw new MalformedXmlException(
+                    String.format("Found non-unique purposes in: %s", dataTypeName));
+        }
+        Boolean isCollectionOptional =
+                XmlUtils.getOdBoolEle(
+                        odDataTypeEle, XmlUtils.OD_NAME_IS_COLLECTION_OPTIONAL, false);
+        Boolean isSharingOptional =
+                XmlUtils.getOdBoolEle(odDataTypeEle, XmlUtils.OD_NAME_IS_SHARING_OPTIONAL, false);
+        Boolean ephemeral = XmlUtils.getOdBoolEle(odDataTypeEle, XmlUtils.OD_NAME_EPHEMERAL, false);
+
+        return new DataType(
+                dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral);
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
index 382a1f0d0eca..efdc8d0a5f11 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
@@ -139,4 +139,10 @@ public class DeveloperInfo implements AslMarshallable {
 
         return XmlUtils.listOf(developerInfoEle);
     }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
index b5310bac232a..c3e7ac35c545 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
@@ -57,4 +57,10 @@ public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInf
                 website,
                 appDeveloperRegistryId);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public DeveloperInfo createFromOdElements(List<Element> elements) throws MalformedXmlException {
+        return null;
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
index 8c2186a628e3..576820dac6c6 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
@@ -70,4 +70,10 @@ public class SafetyLabels implements AslMarshallable {
         }
         return XmlUtils.listOf(safetyLabelsEle);
     }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
index 0f7aa8161c64..7e1838f40680 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
@@ -62,4 +62,10 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels>
                                                 false)));
         return new SafetyLabels(version, dataLabels, securityLabels, thirdPartyVerification);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public SafetyLabels createFromOdElements(List<Element> elements) throws MalformedXmlException {
+        return null;
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
index 529b50364255..437343b14605 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
@@ -50,4 +50,10 @@ public class SecurityLabels implements AslMarshallable {
         }
         return XmlUtils.listOf(ele);
     }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
index 84024520035e..9dc4712c33b0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
@@ -41,4 +41,11 @@ public class SecurityLabelsFactory implements AslMarshallableFactory<SecurityLab
                 XmlUtils.getBoolAttr(ele, XmlUtils.HR_ATTR_IS_DATA_ENCRYPTED, false);
         return new SecurityLabels(isDataDeletable, isDataEncrypted);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public SecurityLabels createFromOdElements(List<Element> elements)
+            throws MalformedXmlException {
+        return null;
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
index 595d748b59af..f0ecf93f2805 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
@@ -46,4 +46,10 @@ public class SystemAppSafetyLabel implements AslMarshallable {
                 XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
         return XmlUtils.listOf(systemAppSafetyLabelEle);
     }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
index f99955993d6c..5b7fe32f2735 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
@@ -39,4 +39,11 @@ public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<Syste
         String url = XmlUtils.getStringAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_URL);
         return new SystemAppSafetyLabel(url);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public SystemAppSafetyLabel createFromOdElements(List<Element> elements)
+            throws MalformedXmlException {
+        return null;
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
index a1b22f885fe6..229b00243e0a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
@@ -40,4 +40,10 @@ public class ThirdPartyVerification implements AslMarshallable {
         ele.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
         return XmlUtils.listOf(ele);
     }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
index c3e4964c0b9d..ac4d3836bcbd 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
@@ -40,4 +40,11 @@ public class ThirdPartyVerificationFactory
         String url = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_URL);
         return new ThirdPartyVerification(url);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public ThirdPartyVerification createFromOdElements(List<Element> elements)
+            throws MalformedXmlException {
+        return null;
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
index ddd3557616ca..ce7ef16ea54e 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
@@ -57,4 +57,10 @@ public class TransparencyInfo implements AslMarshallable {
         }
         return XmlUtils.listOf(transparencyInfoEle);
     }
+
+    /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
+    @Override
+    public List<Element> toHrDomElements(Document doc) {
+        return List.of();
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
index d9c2af41fcac..123de01e57ba 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
@@ -49,4 +49,11 @@ public class TransparencyInfoFactory implements AslMarshallableFactory<Transpare
 
         return new TransparencyInfo(developerInfo, appInfo);
     }
+
+    /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+    @Override
+    public TransparencyInfo createFromOdElements(List<Element> elements)
+            throws MalformedXmlException {
+        return null;
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index ed3d7f8372d6..4f21b0c0ffad 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -16,8 +16,10 @@
 
 package com.android.asllib.util;
 
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
 import java.util.ArrayList;
@@ -118,59 +120,37 @@ public class XmlUtils {
     public static final String TRUE_STR = "true";
     public static final String FALSE_STR = "false";
 
-    /** Gets the single top-level {@link Element} having the {@param tagName}. */
-    public static Element getSingleElement(Document doc, String tagName)
-            throws MalformedXmlException {
-        var elements = doc.getElementsByTagName(tagName);
-        return getSingleElement(elements, tagName);
+    /** Gets the top-level children with the tag name.. */
+    public static List<Element> getChildrenByTagName(Node parentEle, String tagName) {
+        var elements = XmlUtils.asElementList(parentEle.getChildNodes());
+        return elements.stream().filter(e -> e.getTagName().equals(tagName)).toList();
     }
 
     /**
      * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
      */
-    public static Element getSingleChildElement(Element parentEle, String tagName)
+    public static Element getSingleChildElement(Node parentEle, String tagName, boolean required)
             throws MalformedXmlException {
-        var elements = parentEle.getElementsByTagName(tagName);
-        return getSingleElement(elements, tagName, true);
-    }
+        String parentTagNameForErrorMsg =
+                (parentEle instanceof Element) ? ((Element) parentEle).getTagName() : "Node";
+        var elements = getChildrenByTagName(parentEle, tagName);
 
-    /**
-     * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
-     */
-    public static Element getSingleChildElement(Element parentEle, String tagName, boolean required)
-            throws MalformedXmlException {
-        var elements = parentEle.getElementsByTagName(tagName);
-        return getSingleElement(elements, tagName, required);
-    }
-
-    /** Gets the single {@link Element} from {@param elements} */
-    public static Element getSingleElement(NodeList elements, String tagName)
-            throws MalformedXmlException {
-        return getSingleElement(elements, tagName, true);
-    }
-
-    /** Gets the single {@link Element} from {@param elements} */
-    public static Element getSingleElement(NodeList elements, String tagName, boolean required)
-            throws MalformedXmlException {
-        if (elements.getLength() > 1) {
+        if (elements.size() > 1) {
             throw new MalformedXmlException(
                     String.format(
-                            "Expected 1 element \"%s\" in NodeList but got %s.",
-                            tagName, elements.getLength()));
-        } else if (elements.getLength() == 0) {
+                            "Expected 1 %s in %s but got %s.",
+                            tagName, parentTagNameForErrorMsg, elements.size()));
+        } else if (elements.isEmpty()) {
             if (required) {
                 throw new MalformedXmlException(
-                        String.format("Found no element \"%s\" in NodeList.", tagName));
+                        String.format(
+                                "Expected 1 %s in %s but got 0.",
+                                tagName, parentTagNameForErrorMsg));
             } else {
                 return null;
             }
         }
-        var elementAsNode = elements.item(0);
-        if (!(elementAsNode instanceof Element)) {
-            throw new MalformedXmlException(
-                    String.format("%s was not a valid XML element.", tagName));
-        }
-        return ((Element) elementAsNode);
+        return elements.get(0);
     }
 
     /** Gets the single {@link Element} within {@param elements}. */
@@ -229,6 +209,13 @@ public class XmlUtils {
         return ele;
     }
 
+    /** Sets human-readable bool attribute if non-null. */
+    public static void maybeSetHrBoolAttr(Element ele, String attrName, Boolean b) {
+        if (b != null) {
+            ele.setAttribute(attrName, String.valueOf(b));
+        }
+    }
+
     /** Create an on-device Long DOM Element with the given attribute name. */
     public static Element createOdLongEle(Document doc, String name, long l) {
         var ele = doc.createElement(XmlUtils.OD_TAG_LONG);
@@ -303,6 +290,57 @@ public class XmlUtils {
         return b;
     }
 
+    /** Gets a Boolean attribute. */
+    public static Boolean getOdBoolEle(Element ele, String nameName, boolean required)
+            throws MalformedXmlException {
+        List<Element> boolEles =
+                XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_BOOLEAN).stream()
+                        .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
+                        .toList();
+        if (boolEles.size() > 1) {
+            throw new MalformedXmlException(
+                    String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+        }
+        if (boolEles.isEmpty()) {
+            if (required) {
+                throw new MalformedXmlException(
+                        String.format("Found no %s in %s.", nameName, ele.getTagName()));
+            }
+            return null;
+        }
+        Element boolEle = boolEles.get(0);
+
+        Boolean b = XmlUtils.fromString(boolEle.getAttribute(XmlUtils.OD_ATTR_VALUE));
+        if (b == null && required) {
+            throw new MalformedXmlException(
+                    String.format(
+                            "Boolean %s was required but missing, in %s.",
+                            nameName, ele.getTagName()));
+        }
+        return b;
+    }
+
+    /** Gets a OD Pbundle Element attribute with the specified name. */
+    public static Element getOdPbundleWithName(Element ele, String nameName, boolean required)
+            throws MalformedXmlException {
+        List<Element> eles =
+                XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_PBUNDLE_AS_MAP).stream()
+                        .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
+                        .toList();
+        if (eles.size() > 1) {
+            throw new MalformedXmlException(
+                    String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+        }
+        if (eles.isEmpty()) {
+            if (required) {
+                throw new MalformedXmlException(
+                        String.format("Found no %s in %s.", nameName, ele.getTagName()));
+            }
+            return null;
+        }
+        return eles.get(0);
+    }
+
     /** Gets a required String attribute. */
     public static String getStringAttr(Element ele, String attrName) throws MalformedXmlException {
         return getStringAttr(ele, attrName, true);
@@ -325,6 +363,33 @@ public class XmlUtils {
         return s;
     }
 
+    /** Gets on-device style int array. */
+    public static List<Integer> getOdIntArray(Element ele, String nameName, boolean required)
+            throws MalformedXmlException {
+        List<Element> intArrayEles =
+                XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_INT_ARRAY).stream()
+                        .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
+                        .toList();
+        if (intArrayEles.size() > 1) {
+            throw new MalformedXmlException(
+                    String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+        }
+        if (intArrayEles.isEmpty()) {
+            if (required) {
+                throw new MalformedXmlException(
+                        String.format("Found no %s in %s.", nameName, ele.getTagName()));
+            }
+            return List.of();
+        }
+        Element intArrayEle = intArrayEles.get(0);
+        List<Element> itemEles = XmlUtils.getChildrenByTagName(intArrayEle, XmlUtils.OD_TAG_ITEM);
+        List<Integer> ints = new ArrayList<Integer>();
+        for (Element itemEle : itemEles) {
+            ints.add(Integer.parseInt(XmlUtils.getStringAttr(itemEle, XmlUtils.OD_ATTR_VALUE)));
+        }
+        return ints;
+    }
+
     /**
      * Utility method for making a List from one element, to support easier refactoring if needed.
      * For example, List.of() doesn't support null elements.
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
index 2be447e182b2..6f6f2545a5d2 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
@@ -41,6 +41,30 @@ public class DataLabelsTest {
     private static final String SHARED_INVALID_BOOL_FILE_NAME =
             "data-labels-shared-invalid-bool.xml";
 
+    private static final String ACTIONS_IN_APP_FILE_NAME = "data-category-actions-in-app.xml";
+    private static final String APP_PERFORMANCE_FILE_NAME = "data-category-app-performance.xml";
+    private static final String AUDIO_FILE_NAME = "data-category-audio.xml";
+    private static final String CALENDAR_FILE_NAME = "data-category-calendar.xml";
+    private static final String CONTACTS_FILE_NAME = "data-category-contacts.xml";
+    private static final String EMAIL_TEXT_MESSAGE_FILE_NAME =
+            "data-category-email-text-message.xml";
+    private static final String FINANCIAL_FILE_NAME = "data-category-financial.xml";
+    private static final String HEALTH_FITNESS_FILE_NAME = "data-category-health-fitness.xml";
+    private static final String IDENTIFIERS_FILE_NAME = "data-category-identifiers.xml";
+    private static final String LOCATION_FILE_NAME = "data-category-location.xml";
+    private static final String PERSONAL_FILE_NAME = "data-category-personal.xml";
+    private static final String PERSONAL_PARTIAL_FILE_NAME = "data-category-personal-partial.xml";
+    private static final String PHOTO_VIDEO_FILE_NAME = "data-category-photo-video.xml";
+    private static final String SEARCH_AND_BROWSING_FILE_NAME =
+            "data-category-search-and-browsing.xml";
+    private static final String STORAGE_FILE_NAME = "data-category-storage.xml";
+    private static final String PERSONAL_MISSING_PURPOSE_FILE_NAME =
+            "data-category-personal-missing-purpose.xml";
+    private static final String PERSONAL_EMPTY_PURPOSE_FILE_NAME =
+            "data-category-personal-empty-purpose.xml";
+    private static final String UNRECOGNIZED_FILE_NAME = "data-category-unrecognized.xml";
+    private static final String UNRECOGNIZED_TYPE_FILE_NAME = "data-category-unrecognized-type.xml";
+
     private Document mDoc = null;
 
     @Before
@@ -54,6 +78,7 @@ public class DataLabelsTest {
     public void testDataLabelsAccessedValidBool() throws Exception {
         System.out.println("starting testDataLabelsAccessedValidBool.");
         testHrToOdDataLabels(ACCESSED_VALID_BOOL_FILE_NAME);
+        testOdToHrDataLabels(ACCESSED_VALID_BOOL_FILE_NAME);
     }
 
     /** Test for data labels accessed invalid bool. */
@@ -68,6 +93,7 @@ public class DataLabelsTest {
     public void testDataLabelsCollectedValidBool() throws Exception {
         System.out.println("starting testDataLabelsCollectedValidBool.");
         testHrToOdDataLabels(COLLECTED_VALID_BOOL_FILE_NAME);
+        testOdToHrDataLabels(COLLECTED_VALID_BOOL_FILE_NAME);
     }
 
     /** Test for data labels collected invalid bool. */
@@ -75,6 +101,7 @@ public class DataLabelsTest {
     public void testDataLabelsCollectedInvalidBool() throws Exception {
         System.out.println("starting testDataLabelsCollectedInvalidBool.");
         hrToOdExpectException(COLLECTED_INVALID_BOOL_FILE_NAME);
+        odToHrExpectException(COLLECTED_INVALID_BOOL_FILE_NAME);
     }
 
     /** Test for data labels shared valid bool. */
@@ -82,6 +109,7 @@ public class DataLabelsTest {
     public void testDataLabelsSharedValidBool() throws Exception {
         System.out.println("starting testDataLabelsSharedValidBool.");
         testHrToOdDataLabels(SHARED_VALID_BOOL_FILE_NAME);
+        testOdToHrDataLabels(SHARED_VALID_BOOL_FILE_NAME);
     }
 
     /** Test for data labels shared invalid bool. */
@@ -91,12 +119,207 @@ public class DataLabelsTest {
         hrToOdExpectException(SHARED_INVALID_BOOL_FILE_NAME);
     }
 
+    /* Data categories bidirectional tests... */
+
+    /** Test for data labels actions in app. */
+    @Test
+    public void testDataLabelsActionsInApp() throws Exception {
+        System.out.println("starting testDataLabelsActionsInApp.");
+        testHrToOdDataLabels(ACTIONS_IN_APP_FILE_NAME);
+        testOdToHrDataLabels(ACTIONS_IN_APP_FILE_NAME);
+    }
+
+    /** Test for data labels app performance. */
+    @Test
+    public void testDataLabelsAppPerformance() throws Exception {
+        System.out.println("starting testDataLabelsAppPerformance.");
+        testHrToOdDataLabels(APP_PERFORMANCE_FILE_NAME);
+        testOdToHrDataLabels(APP_PERFORMANCE_FILE_NAME);
+    }
+
+    /** Test for data labels audio. */
+    @Test
+    public void testDataLabelsAudio() throws Exception {
+        System.out.println("starting testDataLabelsAudio.");
+        testHrToOdDataLabels(AUDIO_FILE_NAME);
+        testOdToHrDataLabels(AUDIO_FILE_NAME);
+    }
+
+    /** Test for data labels calendar. */
+    @Test
+    public void testDataLabelsCalendar() throws Exception {
+        System.out.println("starting testDataLabelsCalendar.");
+        testHrToOdDataLabels(CALENDAR_FILE_NAME);
+        testOdToHrDataLabels(CALENDAR_FILE_NAME);
+    }
+
+    /** Test for data labels contacts. */
+    @Test
+    public void testDataLabelsContacts() throws Exception {
+        System.out.println("starting testDataLabelsContacts.");
+        testHrToOdDataLabels(CONTACTS_FILE_NAME);
+        testOdToHrDataLabels(CONTACTS_FILE_NAME);
+    }
+
+    /** Test for data labels email text message. */
+    @Test
+    public void testDataLabelsEmailTextMessage() throws Exception {
+        System.out.println("starting testDataLabelsEmailTextMessage.");
+        testHrToOdDataLabels(EMAIL_TEXT_MESSAGE_FILE_NAME);
+        testOdToHrDataLabels(EMAIL_TEXT_MESSAGE_FILE_NAME);
+    }
+
+    /** Test for data labels financial. */
+    @Test
+    public void testDataLabelsFinancial() throws Exception {
+        System.out.println("starting testDataLabelsFinancial.");
+        testHrToOdDataLabels(FINANCIAL_FILE_NAME);
+        testOdToHrDataLabels(FINANCIAL_FILE_NAME);
+    }
+
+    /** Test for data labels health fitness. */
+    @Test
+    public void testDataLabelsHealthFitness() throws Exception {
+        System.out.println("starting testDataLabelsHealthFitness.");
+        testHrToOdDataLabels(HEALTH_FITNESS_FILE_NAME);
+        testOdToHrDataLabels(HEALTH_FITNESS_FILE_NAME);
+    }
+
+    /** Test for data labels identifiers. */
+    @Test
+    public void testDataLabelsIdentifiers() throws Exception {
+        System.out.println("starting testDataLabelsIdentifiers.");
+        testHrToOdDataLabels(IDENTIFIERS_FILE_NAME);
+        testOdToHrDataLabels(IDENTIFIERS_FILE_NAME);
+    }
+
+    /** Test for data labels location. */
+    @Test
+    public void testDataLabelsLocation() throws Exception {
+        System.out.println("starting testDataLabelsLocation.");
+        testHrToOdDataLabels(LOCATION_FILE_NAME);
+        testOdToHrDataLabels(LOCATION_FILE_NAME);
+    }
+
+    /** Test for data labels personal. */
+    @Test
+    public void testDataLabelsPersonal() throws Exception {
+        System.out.println("starting testDataLabelsPersonal.");
+        testHrToOdDataLabels(PERSONAL_FILE_NAME);
+        testOdToHrDataLabels(PERSONAL_FILE_NAME);
+    }
+
+    /** Test for data labels personal partial. */
+    @Test
+    public void testDataLabelsPersonalPartial() throws Exception {
+        System.out.println("starting testDataLabelsPersonalPartial.");
+        testHrToOdDataLabels(PERSONAL_PARTIAL_FILE_NAME);
+        testOdToHrDataLabels(PERSONAL_PARTIAL_FILE_NAME);
+    }
+
+    /** Test for data labels photo video. */
+    @Test
+    public void testDataLabelsPhotoVideo() throws Exception {
+        System.out.println("starting testDataLabelsPhotoVideo.");
+        testHrToOdDataLabels(PHOTO_VIDEO_FILE_NAME);
+        testOdToHrDataLabels(PHOTO_VIDEO_FILE_NAME);
+    }
+
+    /** Test for data labels search and browsing. */
+    @Test
+    public void testDataLabelsSearchAndBrowsing() throws Exception {
+        System.out.println("starting testDataLabelsSearchAndBrowsing.");
+        testHrToOdDataLabels(SEARCH_AND_BROWSING_FILE_NAME);
+        testOdToHrDataLabels(SEARCH_AND_BROWSING_FILE_NAME);
+    }
+
+    /** Test for data labels storage. */
+    @Test
+    public void testDataLabelsStorage() throws Exception {
+        System.out.println("starting testDataLabelsStorage.");
+        testHrToOdDataLabels(STORAGE_FILE_NAME);
+        testOdToHrDataLabels(STORAGE_FILE_NAME);
+    }
+
+    /** Test for data labels hr unrecognized data category. */
+    @Test
+    public void testDataLabelsHrUnrecognizedDataCategory() throws Exception {
+        System.out.println("starting testDataLabelsHrUnrecognizedDataCategory.");
+        hrToOdExpectException(UNRECOGNIZED_FILE_NAME);
+    }
+
+    /** Test for data labels hr unrecognized data type. */
+    @Test
+    public void testDataLabelsHrUnrecognizedDataType() throws Exception {
+        System.out.println("starting testDataLabelsHrUnrecognizedDataType.");
+        hrToOdExpectException(UNRECOGNIZED_TYPE_FILE_NAME);
+    }
+
+    /** Test for data labels hr missing purpose. */
+    @Test
+    public void testDataLabelsHrMissingPurpose() throws Exception {
+        System.out.println("starting testDataLabelsHrMissingPurpose.");
+        hrToOdExpectException(PERSONAL_MISSING_PURPOSE_FILE_NAME);
+    }
+
+    /** Test for data labels hr empty purpose. */
+    @Test
+    public void testDataLabelsHrEmptyPurpose() throws Exception {
+        System.out.println("starting testDataLabelsHrEmptyPurpose.");
+        hrToOdExpectException(PERSONAL_EMPTY_PURPOSE_FILE_NAME);
+    }
+
+    /** Test for data labels od unrecognized data category. */
+    @Test
+    public void testDataLabelsOdUnrecognizedDataCategory() throws Exception {
+        System.out.println("starting testDataLabelsOdUnrecognizedDataCategory.");
+        odToHrExpectException(UNRECOGNIZED_FILE_NAME);
+    }
+
+    /** Test for data labels od unrecognized data type. */
+    @Test
+    public void testDataLabelsOdUnrecognizedDataType() throws Exception {
+        System.out.println("starting testDataLabelsOdUnrecognizedDataCategory.");
+        odToHrExpectException(UNRECOGNIZED_TYPE_FILE_NAME);
+    }
+
+    /** Test for data labels od missing purpose. */
+    @Test
+    public void testDataLabelsOdMissingPurpose() throws Exception {
+        System.out.println("starting testDataLabelsOdMissingPurpose.");
+        odToHrExpectException(PERSONAL_MISSING_PURPOSE_FILE_NAME);
+    }
+
+    /** Test for data labels od empty purpose. */
+    @Test
+    public void testDataLabelsOdEmptyPurpose() throws Exception {
+        System.out.println("starting testDataLabelsOdEmptyPurpose.");
+        odToHrExpectException(PERSONAL_EMPTY_PURPOSE_FILE_NAME);
+    }
+
     private void hrToOdExpectException(String fileName) {
         TestUtils.hrToOdExpectException(new DataLabelsFactory(), DATA_LABELS_HR_PATH, fileName);
     }
 
+    private void odToHrExpectException(String fileName) {
+        TestUtils.odToHrExpectException(new DataLabelsFactory(), DATA_LABELS_OD_PATH, fileName);
+    }
+
     private void testHrToOdDataLabels(String fileName) throws Exception {
         TestUtils.testHrToOd(
-                mDoc, new DataLabelsFactory(), DATA_LABELS_HR_PATH, DATA_LABELS_OD_PATH, fileName);
+                TestUtils.document(),
+                new DataLabelsFactory(),
+                DATA_LABELS_HR_PATH,
+                DATA_LABELS_OD_PATH,
+                fileName);
+    }
+
+    private void testOdToHrDataLabels(String fileName) throws Exception {
+        TestUtils.testOdToHr(
+                TestUtils.document(),
+                new DataLabelsFactory(),
+                DATA_LABELS_OD_PATH,
+                DATA_LABELS_HR_PATH,
+                fileName);
     }
 }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
index faea340ae7bd..6a29b869be43 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
@@ -26,6 +26,8 @@ import com.android.asllib.util.XmlUtils;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
 import java.io.ByteArrayInputStream;
@@ -72,7 +74,7 @@ public class TestUtils {
                             .findFirst()
                             .get()
                             .getTagName();
-            return XmlUtils.asElementList(root.getElementsByTagName(tagName));
+            return XmlUtils.getChildrenByTagName(root, tagName);
         } else {
             return List.of(root);
         }
@@ -105,7 +107,7 @@ public class TestUtils {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setNamespaceAware(true);
         Document document = factory.newDocumentBuilder().parse(stream);
-
+        stripEmptyElements(document);
         return docToStr(document, omitXmlDeclaration);
     }
 
@@ -125,6 +127,17 @@ public class TestUtils {
                 });
     }
 
+    /** Helper for testing on-device to human-readable conversion expecting exception */
+    public static <T extends AslMarshallable> void odToHrExpectException(
+            AslMarshallableFactory<T> factory, String odFolderPath, String fileName) {
+        assertThrows(
+                MalformedXmlException.class,
+                () -> {
+                    factory.createFromOdElements(
+                            TestUtils.getElementsFromResource(Paths.get(odFolderPath, fileName)));
+                });
+    }
+
     /** Helper for testing human-readable to on-device conversion */
     public static <T extends AslMarshallable> void testHrToOd(
             Document doc,
@@ -133,20 +146,71 @@ public class TestUtils {
             String odFolderPath,
             String fileName)
             throws Exception {
+        testFormatToFormat(doc, factory, hrFolderPath, odFolderPath, fileName, true);
+    }
+
+    /** Helper for testing on-device to human-readable conversion */
+    public static <T extends AslMarshallable> void testOdToHr(
+            Document doc,
+            AslMarshallableFactory<T> factory,
+            String odFolderPath,
+            String hrFolderPath,
+            String fileName)
+            throws Exception {
+        testFormatToFormat(doc, factory, odFolderPath, hrFolderPath, fileName, false);
+    }
+
+    /** Helper for testing format to format conversion */
+    private static <T extends AslMarshallable> void testFormatToFormat(
+            Document doc,
+            AslMarshallableFactory<T> factory,
+            String inFolderPath,
+            String outFolderPath,
+            String fileName,
+            boolean hrToOd)
+            throws Exception {
         AslMarshallable marshallable =
-                factory.createFromHrElements(
-                        TestUtils.getElementsFromResource(Paths.get(hrFolderPath, fileName)));
+                hrToOd
+                        ? factory.createFromHrElements(
+                                TestUtils.getElementsFromResource(
+                                        Paths.get(inFolderPath, fileName)))
+                        : factory.createFromOdElements(
+                                TestUtils.getElementsFromResource(
+                                        Paths.get(inFolderPath, fileName)));
+
+        List<Element> elements =
+                hrToOd ? marshallable.toOdDomElements(doc) : marshallable.toHrDomElements(doc);
+        if (elements.isEmpty()) {
+            throw new IllegalStateException("elements was empty.");
+        } else if (elements.size() == 1) {
+            doc.appendChild(elements.get(0));
+        } else {
+            Element root = doc.createElement(TestUtils.HOLDER_TAG_NAME);
+            for (var child : elements) {
+                root.appendChild(child);
+            }
+            doc.appendChild(root);
+        }
+        String converted = TestUtils.getFormattedXml(TestUtils.docToStr(doc, true), true);
+        System.out.println("Converted: " + converted);
+        String expectedOutContents =
+                TestUtils.getFormattedXml(
+                        TestUtils.readStrFromResource(Paths.get(outFolderPath, fileName)), true);
+        System.out.println("Expected: " + expectedOutContents);
+        assertEquals(expectedOutContents, converted);
+    }
 
-        for (var child : marshallable.toOdDomElements(doc)) {
-            doc.appendChild(child);
+    private static void stripEmptyElements(Node node) {
+        NodeList children = node.getChildNodes();
+        for (int i = 0; i < children.getLength(); ++i) {
+            Node child = children.item(i);
+            if (child.getNodeType() == Node.TEXT_NODE) {
+                if (child.getTextContent().trim().length() == 0) {
+                    child.getParentNode().removeChild(child);
+                    i--;
+                }
+            }
+            stripEmptyElements(child);
         }
-        String converted = TestUtils.docToStr(doc, true);
-        System.out.println("converted: " + converted);
-
-        String expectedOdContents =
-                TestUtils.readStrFromResource(Paths.get(odFolderPath, fileName));
-        assertEquals(
-                TestUtils.getFormattedXml(expectedOdContents, true),
-                TestUtils.getFormattedXml(converted, true));
     }
 }
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml
new file mode 100644
index 000000000000..68e191e8ebe3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml
@@ -0,0 +1,17 @@
+<data-labels>
+    <data-shared dataCategory="actions_in_app"
+        dataType="user_interaction"
+        purposes="analytics" />
+    <data-shared dataCategory="actions_in_app"
+        dataType="in_app_search_history"
+        purposes="analytics" />
+    <data-shared dataCategory="actions_in_app"
+        dataType="installed_apps"
+        purposes="analytics" />
+    <data-shared dataCategory="actions_in_app"
+        dataType="user_generated_content"
+        purposes="analytics" />
+    <data-shared dataCategory="actions_in_app"
+        dataType="other"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml
new file mode 100644
index 000000000000..a6bd17d63704
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml
@@ -0,0 +1,11 @@
+<data-labels>
+    <data-shared dataCategory="app_performance"
+        dataType="crash_logs"
+        purposes="analytics" />
+    <data-shared dataCategory="app_performance"
+        dataType="performance_diagnostics"
+        purposes="analytics" />
+    <data-shared dataCategory="app_performance"
+        dataType="other"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml
new file mode 100644
index 000000000000..6274604f3431
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml
@@ -0,0 +1,11 @@
+<data-labels>
+    <data-shared dataCategory="audio"
+        dataType="sound_recordings"
+        purposes="analytics" />
+    <data-shared dataCategory="audio"
+        dataType="music_files"
+        purposes="analytics" />
+    <data-shared dataCategory="audio"
+        dataType="other"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml
new file mode 100644
index 000000000000..f7201f625629
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml
@@ -0,0 +1,5 @@
+<data-labels>
+    <data-shared dataCategory="calendar"
+        dataType="calendar"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml
new file mode 100644
index 000000000000..e8d40be91d90
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml
@@ -0,0 +1,5 @@
+<data-labels>
+    <data-shared dataCategory="contacts"
+        dataType="contacts"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml
new file mode 100644
index 000000000000..69e9b87db287
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml
@@ -0,0 +1,11 @@
+<data-labels>
+    <data-shared dataCategory="email_text_message"
+        dataType="emails"
+        purposes="analytics" />
+    <data-shared dataCategory="email_text_message"
+        dataType="text_messages"
+        purposes="analytics" />
+    <data-shared dataCategory="email_text_message"
+        dataType="other"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml
new file mode 100644
index 000000000000..fdd84569a5df
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml
@@ -0,0 +1,14 @@
+<data-labels>
+    <data-shared dataCategory="financial"
+        dataType="card_bank_account"
+        purposes="analytics" />
+    <data-shared dataCategory="financial"
+    dataType="purchase_history"
+    purposes="analytics" />
+    <data-shared dataCategory="financial"
+        dataType="credit_score"
+        purposes="analytics" />
+    <data-shared dataCategory="financial"
+        dataType="other"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml
new file mode 100644
index 000000000000..bac58e6fb7df
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml
@@ -0,0 +1,8 @@
+<data-labels>
+    <data-shared dataCategory="health_fitness"
+        dataType="health"
+        purposes="analytics" />
+    <data-shared dataCategory="health_fitness"
+        dataType="fitness"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml
new file mode 100644
index 000000000000..ee45f269eb7f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml
@@ -0,0 +1,5 @@
+<data-labels>
+    <data-shared dataCategory="identifiers"
+        dataType="other"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml
new file mode 100644
index 000000000000..e8e59118f69c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml
@@ -0,0 +1,8 @@
+<data-labels>
+    <data-shared dataCategory="location"
+        dataType="approx_location"
+        purposes="analytics" />
+    <data-shared dataCategory="location"
+        dataType="precise_location"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml
new file mode 100644
index 000000000000..0b220f43e5af
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml
@@ -0,0 +1,5 @@
+<data-labels>
+    <data-shared dataCategory="personal"
+    dataType="email_address"
+    purposes="" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml
new file mode 100644
index 000000000000..ac221f208577
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml
@@ -0,0 +1,4 @@
+<data-labels>
+    <data-shared dataCategory="personal"
+    dataType="email_address" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml
new file mode 100644
index 000000000000..11b73689230b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml
@@ -0,0 +1,8 @@
+<data-labels>
+    <data-shared dataCategory="personal"
+        dataType="name"
+        purposes="analytics|developer_communications" />
+    <data-shared dataCategory="personal"
+    dataType="email_address"
+    purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml
new file mode 100644
index 000000000000..f1fbd56571b2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml
@@ -0,0 +1,5 @@
+<data-labels>
+    <data-shared dataCategory="personal"
+    dataType="unrecognized"
+    purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml
new file mode 100644
index 000000000000..59074628de70
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml
@@ -0,0 +1,31 @@
+<data-labels>
+    <data-shared dataCategory="personal"
+        dataType="name"
+        ephemeral="true"
+        isSharingOptional="true"
+        purposes="analytics|developer_communications" />
+    <data-shared dataCategory="personal"
+    dataType="email_address"
+    purposes="analytics" />
+    <data-shared dataCategory="personal"
+    dataType="physical_address"
+    purposes="analytics" />
+    <data-shared dataCategory="personal"
+    dataType="phone_number"
+    purposes="analytics" />
+    <data-shared dataCategory="personal"
+    dataType="race_ethnicity"
+    purposes="analytics" />
+    <data-shared dataCategory="personal"
+    dataType="political_or_religious_beliefs"
+    purposes="analytics" />
+    <data-shared dataCategory="personal"
+    dataType="sexual_orientation_or_gender_identity"
+    purposes="analytics" />
+    <data-shared dataCategory="personal"
+    dataType="personal_identifiers"
+    purposes="analytics" />
+    <data-shared dataCategory="personal"
+    dataType="other"
+    purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml
new file mode 100644
index 000000000000..05fe159a1d7c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml
@@ -0,0 +1,8 @@
+<data-labels>
+    <data-shared dataCategory="photo_video"
+        dataType="photos"
+        purposes="analytics" />
+    <data-shared dataCategory="photo_video"
+        dataType="videos"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml
new file mode 100644
index 000000000000..a5de7bed4152
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml
@@ -0,0 +1,5 @@
+<data-labels>
+    <data-shared dataCategory="search_and_browsing"
+        dataType="web_browsing_history"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml
new file mode 100644
index 000000000000..f01e2df8d99a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml
@@ -0,0 +1,5 @@
+<data-labels>
+    <data-shared dataCategory="storage"
+        dataType="files_docs"
+        purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml
new file mode 100644
index 000000000000..f1fbd56571b2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml
@@ -0,0 +1,5 @@
+<data-labels>
+    <data-shared dataCategory="personal"
+    dataType="unrecognized"
+    purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml
new file mode 100644
index 000000000000..c5be68424226
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml
@@ -0,0 +1,5 @@
+<data-labels>
+    <data-shared dataCategory="unrecognized"
+    dataType="email_address"
+    purposes="analytics" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml
new file mode 100644
index 000000000000..161057a51b79
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml
@@ -0,0 +1,11 @@
+<data-labels>
+    <data-accessed dataCategory="location"
+        dataType="approx_location"
+        purposes="app_functionality" />
+    <data-collected dataCategory="location"
+        dataType="precise_location"
+        purposes="app_functionality" />
+    <data-shared dataCategory="personal"
+        dataType="name"
+        purposes="app_functionality" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml
new file mode 100644
index 000000000000..c5fef58cb25c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml
@@ -0,0 +1,31 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="actions_in_app">
+            <pbundle_as_map name="user_interaction">
+                <int-array name="purposes" num="1">
+                    <item value="2" />
+                </int-array>
+            </pbundle_as_map>
+            <pbundle_as_map name="in_app_search_history">
+                <int-array name="purposes" num="1">
+                    <item value="2" />
+                </int-array>
+            </pbundle_as_map>
+            <pbundle_as_map name="installed_apps">
+                <int-array name="purposes" num="1">
+                    <item value="2" />
+                </int-array>
+            </pbundle_as_map>
+            <pbundle_as_map name="user_generated_content">
+                <int-array name="purposes" num="1">
+                    <item value="2" />
+                </int-array>
+            </pbundle_as_map>
+            <pbundle_as_map name="other">
+                <int-array name="purposes" num="1">
+                    <item value="2" />
+                </int-array>
+            </pbundle_as_map>
+        </pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml
new file mode 100644
index 000000000000..4570145a0436
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml
@@ -0,0 +1,21 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="app_performance">
+    <pbundle_as_map name="crash_logs">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="performance_diagnostics">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="other">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml
new file mode 100644
index 000000000000..120f626549f7
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml
@@ -0,0 +1,21 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="audio">
+    <pbundle_as_map name="sound_recordings">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="music_files">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="other">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml
new file mode 100644
index 000000000000..59eb93812690
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="calendar">
+    <pbundle_as_map name="calendar">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml
new file mode 100644
index 000000000000..f952bfb4df79
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="contacts">
+    <pbundle_as_map name="contacts">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml
new file mode 100644
index 000000000000..bcaa716559d4
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml
@@ -0,0 +1,21 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="email_text_message">
+    <pbundle_as_map name="emails">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="text_messages">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="other">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml
new file mode 100644
index 000000000000..a7bc82e38dac
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml
@@ -0,0 +1,26 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="financial">
+    <pbundle_as_map name="card_bank_account">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="purchase_history">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="credit_score">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="other">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml
new file mode 100644
index 000000000000..f3ab2bd90733
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml
@@ -0,0 +1,16 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="health_fitness">
+    <pbundle_as_map name="health">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="fitness">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml
new file mode 100644
index 000000000000..05c07e54c5a8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="identifiers">
+    <pbundle_as_map name="other">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml
new file mode 100644
index 000000000000..931d1adc0218
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml
@@ -0,0 +1,16 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="location">
+    <pbundle_as_map name="approx_location">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="precise_location">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml
new file mode 100644
index 000000000000..83f4a67cd54d
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="personal">
+    <pbundle_as_map name="email_address">
+
+        <int-array name="purposes" num="2">
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml
new file mode 100644
index 000000000000..532f5deee655
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml
@@ -0,0 +1,8 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="personal">
+    <pbundle_as_map name="email_address">
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml
new file mode 100644
index 000000000000..14f9ef2b74ad
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml
@@ -0,0 +1,17 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="personal">
+    <pbundle_as_map name="name">
+        <int-array name="purposes" num="2">
+            <item value="2" />
+            <item value="3" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="email_address">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml
new file mode 100644
index 000000000000..1c87de9e67a7
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml
@@ -0,0 +1,54 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="personal">
+    <pbundle_as_map name="name">
+        <int-array name="purposes" num="2">
+            <item value="2" />
+            <item value="3" />
+        </int-array>
+        <boolean name="is_sharing_optional" value="true" />
+        <boolean name="ephemeral" value="true" />
+    </pbundle_as_map>
+    <pbundle_as_map name="email_address">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="physical_address">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="phone_number">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="race_ethnicity">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="political_or_religious_beliefs">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="sexual_orientation_or_gender_identity">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="personal_identifiers">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="other">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml
new file mode 100644
index 000000000000..a752b5152c25
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml
@@ -0,0 +1,16 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="photo_video">
+    <pbundle_as_map name="photos">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+    <pbundle_as_map name="videos">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml
new file mode 100644
index 000000000000..99bc1881b4f1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="search_and_browsing">
+    <pbundle_as_map name="web_browsing_history">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml
new file mode 100644
index 000000000000..a4d2a6249119
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="storage">
+    <pbundle_as_map name="files_docs">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml
new file mode 100644
index 000000000000..16195df53b3a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="personal">
+    <pbundle_as_map name="unrecognized">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml
new file mode 100644
index 000000000000..511940eafd4a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml
@@ -0,0 +1,11 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="unrecognized">
+    <pbundle_as_map name="email_address">
+        <int-array name="purposes" num="1">
+            <item value="2" />
+        </int-array>
+    </pbundle_as_map>
+</pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml
new file mode 100644
index 000000000000..f86dbc1a63b2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml
@@ -0,0 +1,29 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_accessed">
+        <pbundle_as_map name="location">
+            <pbundle_as_map name="approx_location">
+                <int-array name="purposes" num="1">
+                    <item value="1"/>
+                </int-array>
+            </pbundle_as_map>
+        </pbundle_as_map>
+    </pbundle_as_map>
+    <pbundle_as_map name="data_collected">
+        <pbundle_as_map name="location">
+            <pbundle_as_map name="precise_location">
+                <int-array name="purposes" num="1">
+                    <item value="1"/>
+                </int-array>
+            </pbundle_as_map>
+        </pbundle_as_map>
+    </pbundle_as_map>
+    <pbundle_as_map name="data_shared">
+        <pbundle_as_map name="personal">
+            <pbundle_as_map name="name">
+                <int-array name="purposes" num="1">
+                    <item value="1"/>
+                </int-array>
+            </pbundle_as_map>
+        </pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml
new file mode 100644
index 000000000000..54cc8e7049b2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml
@@ -0,0 +1,13 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_collected">
+        <pbundle_as_map name="location">
+            <pbundle_as_map name="approx_location">
+                <int-array name="purposes" num="1">
+                    <item value="1"/>
+                </int-array>
+                <boolean name="is_sharing_optional" value="false"/>
+                <boolean name="ephemeral" value="false"/>
+            </pbundle_as_map>
+        </pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
index d1d4e33e855a..3864f986d20d 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
@@ -1,5 +1,5 @@
 <pbundle_as_map name="data_labels">
-    <pbundle_as_map name="data_shared">
+<pbundle_as_map name="data_shared">
         <pbundle_as_map name="location">
             <pbundle_as_map name="approx_location">
                 <int-array name="purposes" num="1">
-- 
GitLab