From d4c7fa82fecea072471822667718f835bd6f37f6 Mon Sep 17 00:00:00 2001
From: Max Loh <mloh@google.com>
Date: Tue, 26 Mar 2024 06:28:03 -0700
Subject: [PATCH] Adding more supported fields/logic to aslgen.

Includes top-level version number, transparency_info. Refactored non-Marshallable conversion logic from AndroidSafetyLabel to AslConverter. Added logic to support some more non-required fields without throwing an error.

Bug: 329902686
Test: A future CL will add a lot of unit tests, after which unit tests can be written in tandem with implementation.
Change-Id: If4fb73cdcc40abac77ec82e436ca11ebe4feb48d
---
 .../aslgen/java/com/android/aslgen/Main.java  |  28 ++--
 .../android/asllib/AndroidSafetyLabel.java    | 122 +++-----------
 .../asllib/AndroidSafetyLabelFactory.java     |  24 ++-
 .../lib/java/com/android/asllib/AppInfo.java  | 143 ++++++++++++++++
 .../com/android/asllib/AppInfoFactory.java    |  84 ++++++++++
 .../java/com/android/asllib/AslConverter.java | 116 +++++++++++++
 .../java/com/android/asllib/DataCategory.java |   2 +-
 .../android/asllib/DataCategoryFactory.java   |   3 +-
 .../java/com/android/asllib/DataLabels.java   |   2 +-
 .../com/android/asllib/DataLabelsFactory.java |   5 +
 .../lib/java/com/android/asllib/DataType.java |  45 +++--
 .../com/android/asllib/DataTypeFactory.java   |   9 +-
 .../com/android/asllib/DeveloperInfo.java     | 140 ++++++++++++++++
 .../android/asllib/DeveloperInfoFactory.java  |  59 +++++++
 .../java/com/android/asllib/SafetyLabels.java |   8 +-
 .../android/asllib/SafetyLabelsFactory.java   |  13 +-
 .../android/asllib/SystemAppSafetyLabel.java  |  47 ++++++
 .../asllib/SystemAppSafetyLabelFactory.java   |  41 +++++
 .../com/android/asllib/TransparencyInfo.java  |  58 +++++++
 .../asllib/TransparencyInfoFactory.java       |  51 ++++++
 .../lib/java/com/android/asllib/XmlUtils.java | 158 +++++++++++++++++-
 .../com/android/asllib/util/AslgenUtil.java   |  26 +++
 .../java/com/android/aslgen/AslgenTests.java  |   6 +-
 23 files changed, 1021 insertions(+), 169 deletions(-)
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/AslgenUtil.java

diff --git a/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java b/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java
index fb7a6ab42d95..d7edfd44019c 100644
--- a/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java
+++ b/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java
@@ -16,8 +16,8 @@
 
 package com.android.aslgen;
 
-import com.android.asllib.AndroidSafetyLabel;
-import com.android.asllib.AndroidSafetyLabel.Format;
+import com.android.asllib.AslConverter;
+import com.android.asllib.AslConverter.Format;
 import com.android.asllib.util.MalformedXmlException;
 
 import org.xml.sax.SAXException;
@@ -41,9 +41,8 @@ public class Main {
 
         String inFile = null;
         String outFile = null;
-        Format inFormat = Format.NULL;
-        Format outFormat = Format.NULL;
-
+        Format inFormat = AslConverter.Format.NULL;
+        Format outFormat = AslConverter.Format.NULL;
 
         // Except for "--help", all arguments require a value currently.
         // So just make sure we have an even number and
@@ -79,11 +78,11 @@ public class Main {
             throw new IllegalArgumentException("output file is required");
         }
 
-        if (inFormat == Format.NULL) {
+        if (inFormat == AslConverter.Format.NULL) {
             throw new IllegalArgumentException("input format is required");
         }
 
-        if (outFormat == Format.NULL) {
+        if (outFormat == AslConverter.Format.NULL) {
             throw new IllegalArgumentException("output format is required");
         }
 
@@ -92,24 +91,23 @@ public class Main {
         System.out.println("in format: " + inFormat);
         System.out.println("out format: " + outFormat);
 
-        var asl = AndroidSafetyLabel.readFromStream(new FileInputStream(inFile), inFormat);
-        asl.writeToStream(new FileOutputStream(outFile), outFormat);
+        var asl = AslConverter.readFromStream(new FileInputStream(inFile), inFormat);
+        AslConverter.writeToStream(new FileOutputStream(outFile), asl, outFormat);
     }
 
     private static Format getFormat(String argValue) {
         if ("hr".equals(argValue)) {
-            return Format.HUMAN_READABLE;
+            return AslConverter.Format.HUMAN_READABLE;
         } else if ("od".equals(argValue)) {
-            return Format.ON_DEVICE;
+            return AslConverter.Format.ON_DEVICE;
         } else {
-            return Format.NULL;
+            return AslConverter.Format.NULL;
         }
     }
 
     private static void showUsage() {
-        AndroidSafetyLabel.test();
         System.err.println(
-                "Usage:\n"
-        );
+                "Usage: aslgen --in-path [input-file] --out-path [output-file] --in-format [hr|od]"
+                        + " --out-format [hr|od]");
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
index ed9f1ac70a2b..cdb559b52c0e 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
@@ -16,125 +16,47 @@
 
 package com.android.asllib;
 
-import com.android.asllib.util.MalformedXmlException;
-
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
-import org.xml.sax.SAXException;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
 import java.util.List;
 
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
 public class AndroidSafetyLabel implements AslMarshallable {
 
-    public enum Format {
-        NULL, HUMAN_READABLE, ON_DEVICE;
-    }
-
+    private final Long mVersion;
+    private final SystemAppSafetyLabel mSystemAppSafetyLabel;
     private final SafetyLabels mSafetyLabels;
+    private final TransparencyInfo mTransparencyInfo;
 
     public SafetyLabels getSafetyLabels() {
         return mSafetyLabels;
     }
 
-    public AndroidSafetyLabel(SafetyLabels safetyLabels) {
+    public AndroidSafetyLabel(
+            Long version,
+            SystemAppSafetyLabel systemAppSafetyLabel,
+            SafetyLabels safetyLabels,
+            TransparencyInfo transparencyInfo) {
+        this.mVersion = version;
+        this.mSystemAppSafetyLabel = systemAppSafetyLabel;
         this.mSafetyLabels = safetyLabels;
-    }
-
-    /** Reads a {@link AndroidSafetyLabel} from an {@link InputStream}. */
-    // TODO(b/329902686): Support parsing from on-device.
-    public static AndroidSafetyLabel readFromStream(InputStream in, Format format)
-            throws IOException, ParserConfigurationException, SAXException, MalformedXmlException {
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        factory.setNamespaceAware(true);
-        Document document = factory.newDocumentBuilder().parse(in);
-
-        switch (format) {
-            case HUMAN_READABLE:
-                Element appMetadataBundles =
-                        XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
-
-                return new AndroidSafetyLabelFactory()
-                        .createFromHrElements(
-                                List.of(
-                                        XmlUtils.getSingleElement(
-                                                document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES)));
-            case ON_DEVICE:
-                throw new IllegalArgumentException(
-                        "Parsing from on-device format is not supported at this time.");
-            default:
-                throw new IllegalStateException("Unrecognized input format.");
-        }
-    }
-
-    /** Reads a {@link AndroidSafetyLabel} from a String. */
-    public static AndroidSafetyLabel readFromString(String in, Format format)
-            throws IOException, ParserConfigurationException, SAXException, MalformedXmlException {
-        InputStream stream = new ByteArrayInputStream(in.getBytes(StandardCharsets.UTF_8));
-        return readFromStream(stream, format);
-    }
-
-    /** Write the content of the {@link AndroidSafetyLabel} to a {@link OutputStream}. */
-    // TODO(b/329902686): Support outputting human-readable format.
-    public void writeToStream(OutputStream out, Format format)
-            throws IOException, ParserConfigurationException, TransformerException {
-        var docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-        var document = docBuilder.newDocument();
-
-        switch (format) {
-            case HUMAN_READABLE:
-                throw new IllegalArgumentException(
-                        "Outputting human-readable format is not supported at this time.");
-            case ON_DEVICE:
-                for (var child : this.toOdDomElements(document)) {
-                    document.appendChild(child);
-                }
-                break;
-            default:
-                throw new IllegalStateException("Unrecognized input format.");
-        }
-
-        TransformerFactory transformerFactory = TransformerFactory.newInstance();
-        Transformer transformer = transformerFactory.newTransformer();
-        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
-        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
-        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
-        StreamResult streamResult = new StreamResult(out); // out
-        DOMSource domSource = new DOMSource(document);
-        transformer.transform(domSource, streamResult);
-    }
-
-    /** Get the content of the {@link AndroidSafetyLabel} as String. */
-    public String getXmlAsString(Format format)
-            throws IOException, ParserConfigurationException, TransformerException {
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        this.writeToStream(out, format);
-        return out.toString(StandardCharsets.UTF_8);
+        this.mTransparencyInfo = transparencyInfo;
     }
 
     /** Creates an on-device DOM element from an {@link AndroidSafetyLabel} */
     @Override
     public List<Element> toOdDomElements(Document doc) {
         Element aslEle = doc.createElement(XmlUtils.OD_TAG_BUNDLE);
-        XmlUtils.appendChildren(aslEle, mSafetyLabels.toOdDomElements(doc));
-        return List.of(aslEle);
-    }
-
-    public static void test() {
-        // TODO(b/329902686): Add tests.
+        aslEle.appendChild(XmlUtils.createOdLongEle(doc, XmlUtils.OD_NAME_VERSION, mVersion));
+        if (mSafetyLabels != null) {
+            XmlUtils.appendChildren(aslEle, mSafetyLabels.toOdDomElements(doc));
+        }
+        if (mSystemAppSafetyLabel != null) {
+            XmlUtils.appendChildren(aslEle, mSystemAppSafetyLabel.toOdDomElements(doc));
+        }
+        if (mTransparencyInfo != null) {
+            XmlUtils.appendChildren(aslEle, mTransparencyInfo.toOdDomElements(doc));
+        }
+        return XmlUtils.listOf(aslEle);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
index 7e7fcf9c08ba..3dc725b5452b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
@@ -29,11 +29,29 @@ public class AndroidSafetyLabelFactory implements AslMarshallableFactory<Android
     public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles)
             throws MalformedXmlException {
         Element appMetadataBundlesEle = XmlUtils.getSingleElement(appMetadataBundles);
+        long version = XmlUtils.tryGetVersion(appMetadataBundlesEle);
+
         Element safetyLabelsEle =
                 XmlUtils.getSingleChildElement(
-                        appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS);
+                        appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS, false);
         SafetyLabels safetyLabels =
-                new SafetyLabelsFactory().createFromHrElements(List.of(safetyLabelsEle));
-        return new AndroidSafetyLabel(safetyLabels);
+                new SafetyLabelsFactory().createFromHrElements(XmlUtils.listOf(safetyLabelsEle));
+
+        Element systemAppSafetyLabelEle =
+                XmlUtils.getSingleChildElement(
+                        appMetadataBundlesEle, XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL, false);
+        SystemAppSafetyLabel systemAppSafetyLabel =
+                new SystemAppSafetyLabelFactory()
+                        .createFromHrElements(XmlUtils.listOf(systemAppSafetyLabelEle));
+
+        Element transparencyInfoEle =
+                XmlUtils.getSingleChildElement(
+                        appMetadataBundlesEle, XmlUtils.HR_TAG_TRANSPARENCY_INFO, false);
+        TransparencyInfo transparencyInfo =
+                new TransparencyInfoFactory()
+                        .createFromHrElements(XmlUtils.listOf(transparencyInfoEle));
+
+        return new AndroidSafetyLabel(
+                version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java
new file mode 100644
index 000000000000..f94b6591cd10
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** AppInfo representation */
+public class AppInfo implements AslMarshallable {
+    private final String mTitle;
+    private final String mDescription;
+    private final Boolean mContainsAds;
+    private final Boolean mObeyAps;
+    private final Boolean mAdsFingerprinting;
+    private final Boolean mSecurityFingerprinting;
+    private final String mPrivacyPolicy;
+    private final List<String> mSecurityEndpoints;
+    private final List<String> mFirstPartyEndpoints;
+    private final List<String> mServiceProviderEndpoints;
+    private final String mCategory;
+    private final String mEmail;
+    private final String mWebsite;
+
+    public AppInfo(
+            String title,
+            String description,
+            Boolean containsAds,
+            Boolean obeyAps,
+            Boolean adsFingerprinting,
+            Boolean securityFingerprinting,
+            String privacyPolicy,
+            List<String> securityEndpoints,
+            List<String> firstPartyEndpoints,
+            List<String> serviceProviderEndpoints,
+            String category,
+            String email,
+            String website) {
+        this.mTitle = title;
+        this.mDescription = description;
+        this.mContainsAds = containsAds;
+        this.mObeyAps = obeyAps;
+        this.mAdsFingerprinting = adsFingerprinting;
+        this.mSecurityFingerprinting = securityFingerprinting;
+        this.mPrivacyPolicy = privacyPolicy;
+        this.mSecurityEndpoints = securityEndpoints;
+        this.mFirstPartyEndpoints = firstPartyEndpoints;
+        this.mServiceProviderEndpoints = serviceProviderEndpoints;
+        this.mCategory = category;
+        this.mEmail = email;
+        this.mWebsite = website;
+    }
+
+    /** Creates an on-device DOM element from the {@link SafetyLabels}. */
+    @Override
+    public List<Element> toOdDomElements(Document doc) {
+        Element appInfoEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_APP_INFO);
+        if (this.mTitle != null) {
+            appInfoEle.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_TITLE, mTitle));
+        }
+        if (this.mDescription != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_DESCRIPTION, mDescription));
+        }
+        if (this.mContainsAds != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_CONTAINS_ADS, mContainsAds));
+        }
+        if (this.mObeyAps != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_OBEY_APS, mObeyAps));
+        }
+        if (this.mAdsFingerprinting != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdBooleanEle(
+                            doc, XmlUtils.OD_NAME_ADS_FINGERPRINTING, mAdsFingerprinting));
+        }
+        if (this.mSecurityFingerprinting != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdBooleanEle(
+                            doc,
+                            XmlUtils.OD_NAME_SECURITY_FINGERPRINTING,
+                            mSecurityFingerprinting));
+        }
+        if (this.mPrivacyPolicy != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(
+                            doc, XmlUtils.OD_NAME_PRIVACY_POLICY, mPrivacyPolicy));
+        }
+        if (this.mSecurityEndpoints != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdArray(
+                            doc,
+                            XmlUtils.OD_TAG_STRING_ARRAY,
+                            XmlUtils.OD_NAME_SECURITY_ENDPOINT,
+                            mSecurityEndpoints));
+        }
+        if (this.mFirstPartyEndpoints != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdArray(
+                            doc,
+                            XmlUtils.OD_TAG_STRING_ARRAY,
+                            XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINT,
+                            mFirstPartyEndpoints));
+        }
+        if (this.mServiceProviderEndpoints != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdArray(
+                            doc,
+                            XmlUtils.OD_TAG_STRING_ARRAY,
+                            XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINT,
+                            mServiceProviderEndpoints));
+        }
+        if (this.mCategory != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_CATEGORY, this.mCategory));
+        }
+        if (this.mEmail != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, this.mEmail));
+        }
+        if (this.mWebsite != null) {
+            appInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, this.mWebsite));
+        }
+        return XmlUtils.listOf(appInfoEle);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java
new file mode 100644
index 000000000000..26d94c16c7f0
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
+
+    /** Creates a {@link AppInfo} from the human-readable DOM element. */
+    @Override
+    public AppInfo createFromHrElements(List<Element> elements) throws MalformedXmlException {
+        Element appInfoEle = XmlUtils.getSingleElement(elements);
+        if (appInfoEle == null) {
+            AslgenUtil.logI("No AppInfo found in hr format.");
+            return null;
+        }
+
+        String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE);
+        String description = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION);
+        Boolean containsAds = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS);
+        Boolean obeyAps = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS);
+        Boolean adsFingerprinting =
+                XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING);
+        Boolean securityFingerprinting =
+                XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING);
+        String privacyPolicy = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY);
+        List<String> securityEndpoints =
+                Arrays.stream(
+                                appInfoEle
+                                        .getAttribute(XmlUtils.HR_ATTR_SECURITY_ENDPOINTS)
+                                        .split("\\|"))
+                        .toList();
+        List<String> firstPartyEndpoints =
+                Arrays.stream(
+                                appInfoEle
+                                        .getAttribute(XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS)
+                                        .split("\\|"))
+                        .toList();
+        List<String> serviceProviderEndpoints =
+                Arrays.stream(
+                                appInfoEle
+                                        .getAttribute(XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS)
+                                        .split("\\|"))
+                        .toList();
+        String category = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY);
+        String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL);
+        String website = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
+
+        return new AppInfo(
+                title,
+                description,
+                containsAds,
+                obeyAps,
+                adsFingerprinting,
+                securityFingerprinting,
+                privacyPolicy,
+                securityEndpoints,
+                firstPartyEndpoints,
+                serviceProviderEndpoints,
+                category,
+                email,
+                website);
+    }
+}
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
new file mode 100644
index 000000000000..9dd55314e844
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+public class AslConverter {
+    public enum Format {
+        NULL,
+        HUMAN_READABLE,
+        ON_DEVICE;
+    }
+
+    /** Reads a {@link AndroidSafetyLabel} from an {@link InputStream}. */
+    // TODO(b/329902686): Support parsing from on-device.
+    public static AndroidSafetyLabel readFromStream(InputStream in, Format format)
+            throws IOException, ParserConfigurationException, SAXException, MalformedXmlException {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        Document document = factory.newDocumentBuilder().parse(in);
+
+        switch (format) {
+            case HUMAN_READABLE:
+                Element appMetadataBundles =
+                        XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
+
+                return new AndroidSafetyLabelFactory()
+                        .createFromHrElements(XmlUtils.listOf(appMetadataBundles));
+            case ON_DEVICE:
+                throw new IllegalArgumentException(
+                        "Parsing from on-device format is not supported at this time.");
+            default:
+                throw new IllegalStateException("Unrecognized input format.");
+        }
+    }
+
+    /** Reads a {@link AndroidSafetyLabel} from a String. */
+    public static AndroidSafetyLabel readFromString(String in, Format format)
+            throws IOException, ParserConfigurationException, SAXException, MalformedXmlException {
+        InputStream stream = new ByteArrayInputStream(in.getBytes(StandardCharsets.UTF_8));
+        return readFromStream(stream, format);
+    }
+
+    /** Write the content of the {@link AndroidSafetyLabel} to a {@link OutputStream}. */
+    // TODO(b/329902686): Support outputting human-readable format.
+    public static void writeToStream(
+            OutputStream out, AndroidSafetyLabel asl, AslConverter.Format format)
+            throws IOException, ParserConfigurationException, TransformerException {
+        var docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        var document = docBuilder.newDocument();
+
+        switch (format) {
+            case HUMAN_READABLE:
+                throw new IllegalArgumentException(
+                        "Outputting human-readable format is not supported at this time.");
+            case ON_DEVICE:
+                for (var child : asl.toOdDomElements(document)) {
+                    document.appendChild(child);
+                }
+                break;
+            default:
+                throw new IllegalStateException("Unrecognized input format.");
+        }
+
+        TransformerFactory transformerFactory = TransformerFactory.newInstance();
+        Transformer transformer = transformerFactory.newTransformer();
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+        StreamResult streamResult = new StreamResult(out); // out
+        DOMSource domSource = new DOMSource(document);
+        transformer.transform(domSource, streamResult);
+    }
+
+    /** Get the content of the {@link AndroidSafetyLabel} as String. */
+    public static String getXmlAsString(AndroidSafetyLabel asl, AslConverter.Format format)
+            throws IOException, ParserConfigurationException, TransformerException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeToStream(out, asl, format);
+        return out.toString(StandardCharsets.UTF_8);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
index e5ed63b74ebf..b9e06fbdfc7e 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
@@ -53,6 +53,6 @@ public class DataCategory implements AslMarshallable {
         for (DataType dataType : mDataTypes.values()) {
             XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc));
         }
-        return List.of(dataCategoryEle);
+        return XmlUtils.listOf(dataCategoryEle);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
index d9463452d7bc..ae7b603c87f6 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
@@ -36,7 +36,8 @@ public class DataCategoryFactory implements AslMarshallableFactory<DataCategory>
                 throw new MalformedXmlException(
                         String.format("Unrecognized data type name: %s", dataTypeName));
             }
-            dataTypeMap.put(dataTypeName, new DataTypeFactory().createFromHrElements(List.of(ele)));
+            dataTypeMap.put(
+                    dataTypeName, new DataTypeFactory().createFromHrElements(XmlUtils.listOf(ele)));
         }
 
         return new DataCategory(categoryName, dataTypeMap);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
index d2fffc0a36f6..96ec93c28c87 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
@@ -74,7 +74,7 @@ public class DataLabels implements AslMarshallable {
         maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED);
         maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED);
 
-        return List.of(dataLabelsEle);
+        return XmlUtils.listOf(dataLabelsEle);
     }
 
     private void maybeAppendDataUsages(
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
index 1adb140f446d..0e14ebf4eab9 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.asllib;
 
+import com.android.asllib.util.AslgenUtil;
 import com.android.asllib.util.MalformedXmlException;
 
 import org.w3c.dom.Element;
@@ -33,6 +34,10 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
     @Override
     public DataLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
         Element ele = XmlUtils.getSingleElement(elements);
+        if (ele == null) {
+            AslgenUtil.logI("Found no DataLabels in hr format.");
+            return null;
+        }
         Map<String, DataCategory> dataAccessed =
                 getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED);
         Map<String, DataCategory> dataCollected =
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
index 5ba29757e19e..cecee39cbb12 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
@@ -29,15 +29,13 @@ import java.util.Set;
 public class DataType implements AslMarshallable {
 
     public enum Purpose {
-        PURPOSE_APP_FUNCTIONALITY(1),
-        PURPOSE_ANALYTICS(2),
-        PURPOSE_DEVELOPER_COMMUNICATIONS(3),
-        PURPOSE_FRAUD_PREVENTION_SECURITY(4),
-        PURPOSE_ADVERTISING(5),
-        PURPOSE_PERSONALIZATION(6),
-        PURPOSE_ACCOUNT_MANAGEMENT(7);
-
-        private static final String PURPOSE_PREFIX = "PURPOSE_";
+        APP_FUNCTIONALITY(1),
+        ANALYTICS(2),
+        DEVELOPER_COMMUNICATIONS(3),
+        FRAUD_PREVENTION_SECURITY(4),
+        ADVERTISING(5),
+        PERSONALIZATION(6),
+        ACCOUNT_MANAGEMENT(7);
 
         private final int mValue;
 
@@ -57,7 +55,7 @@ public class DataType implements AslMarshallable {
                     return e;
                 }
             }
-            throw new IllegalArgumentException("No enum for value: " + value);
+            throw new IllegalArgumentException("No Purpose enum for value: " + value);
         }
 
         /** Get the Purpose associated with the human-readable String. */
@@ -67,15 +65,12 @@ public class DataType implements AslMarshallable {
                     return e;
                 }
             }
-            throw new IllegalArgumentException("No enum for str: " + s);
+            throw new IllegalArgumentException("No Purpose enum for str: " + s);
         }
 
         /** Human-readable String representation of Purpose. */
         public String toString() {
-            if (!this.name().startsWith(PURPOSE_PREFIX)) {
-                return this.name();
-            }
-            return this.name().substring(PURPOSE_PREFIX.length()).toLowerCase();
+            return this.name().toLowerCase();
         }
     }
 
@@ -139,16 +134,14 @@ public class DataType implements AslMarshallable {
     public List<Element> toOdDomElements(Document doc) {
         Element dataTypeEle = XmlUtils.createPbundleEleWithName(doc, this.getDataTypeName());
         if (!this.getPurposeSet().isEmpty()) {
-            Element purposesEle = doc.createElement(XmlUtils.OD_TAG_INT_ARRAY);
-            purposesEle.setAttribute(XmlUtils.OD_ATTR_NAME, XmlUtils.OD_NAME_PURPOSES);
-            purposesEle.setAttribute(
-                    XmlUtils.OD_ATTR_NUM, String.valueOf(this.getPurposeSet().size()));
-            for (DataType.Purpose purpose : this.getPurposeSet()) {
-                Element purposeEle = doc.createElement(XmlUtils.OD_TAG_ITEM);
-                purposeEle.setAttribute(XmlUtils.OD_ATTR_VALUE, String.valueOf(purpose.getValue()));
-                purposesEle.appendChild(purposeEle);
-            }
-            dataTypeEle.appendChild(purposesEle);
+            dataTypeEle.appendChild(
+                    XmlUtils.createOdArray(
+                            doc,
+                            XmlUtils.OD_TAG_INT_ARRAY,
+                            XmlUtils.OD_NAME_PURPOSES,
+                            this.getPurposeSet().stream()
+                                    .map(p -> String.valueOf(p.getValue()))
+                                    .toList()));
         }
 
         maybeAddBoolToOdElement(
@@ -162,7 +155,7 @@ public class DataType implements AslMarshallable {
                 this.getIsSharingOptional(),
                 XmlUtils.OD_NAME_IS_SHARING_OPTIONAL);
         maybeAddBoolToOdElement(doc, dataTypeEle, this.getEphemeral(), XmlUtils.OD_NAME_EPHEMERAL);
-        return List.of(dataTypeEle);
+        return XmlUtils.listOf(dataTypeEle);
     }
 
     private static void maybeAddBoolToOdElement(
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
index e3d1587d860c..bfa330334487 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
@@ -34,13 +34,10 @@ public class DataTypeFactory implements AslMarshallableFactory<DataType> {
                         .map(DataType.Purpose::forString)
                         .collect(Collectors.toUnmodifiableSet());
         Boolean isCollectionOptional =
-                XmlUtils.fromString(
-                        hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL));
+                XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL);
         Boolean isSharingOptional =
-                XmlUtils.fromString(
-                        hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL));
-        Boolean ephemeral =
-                XmlUtils.fromString(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_EPHEMERAL));
+                XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL);
+        Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL);
         return new DataType(
                 dataTypeName, purposeSet, isCollectionOptional, isSharingOptional, ephemeral);
     }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java
new file mode 100644
index 000000000000..44a5b129e428
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** DeveloperInfo representation */
+public class DeveloperInfo implements AslMarshallable {
+    public enum DeveloperRelationship {
+        OEM(0),
+        ODM(1),
+        SOC(2),
+        OTA(3),
+        CARRIER(4),
+        AOSP(5),
+        OTHER(6);
+
+        private final int mValue;
+
+        DeveloperRelationship(int value) {
+            this.mValue = value;
+        }
+
+        /** Get the int value associated with the DeveloperRelationship. */
+        public int getValue() {
+            return mValue;
+        }
+
+        /** Get the DeveloperRelationship associated with the int value. */
+        public static DeveloperInfo.DeveloperRelationship forValue(int value) {
+            for (DeveloperInfo.DeveloperRelationship e : values()) {
+                if (e.getValue() == value) {
+                    return e;
+                }
+            }
+            throw new IllegalArgumentException("No DeveloperRelationship enum for value: " + value);
+        }
+
+        /** Get the DeveloperRelationship associated with the human-readable String. */
+        public static DeveloperInfo.DeveloperRelationship forString(String s) {
+            for (DeveloperInfo.DeveloperRelationship e : values()) {
+                if (e.toString().equals(s)) {
+                    return e;
+                }
+            }
+            throw new IllegalArgumentException("No DeveloperRelationship enum for str: " + s);
+        }
+
+        /** Human-readable String representation of DeveloperRelationship. */
+        public String toString() {
+            return this.name().toLowerCase();
+        }
+    }
+
+    private final String mName;
+    private final String mEmail;
+    private final String mAddress;
+    private final String mCountryRegion;
+    private final DeveloperRelationship mDeveloperRelationship;
+    private final String mWebsite;
+    private final String mAppDeveloperRegistryId;
+
+    public DeveloperInfo(
+            String name,
+            String email,
+            String address,
+            String countryRegion,
+            DeveloperRelationship developerRelationship,
+            String website,
+            String appDeveloperRegistryId) {
+        this.mName = name;
+        this.mEmail = email;
+        this.mAddress = address;
+        this.mCountryRegion = countryRegion;
+        this.mDeveloperRelationship = developerRelationship;
+        this.mWebsite = website;
+        this.mAppDeveloperRegistryId = appDeveloperRegistryId;
+    }
+
+    /** Creates an on-device DOM element from the {@link SafetyLabels}. */
+    @Override
+    public List<Element> toOdDomElements(Document doc) {
+        Element developerInfoEle =
+                XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DEVELOPER_INFO);
+        if (mName != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_NAME, mName));
+        }
+        if (mEmail != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, mEmail));
+        }
+        if (mAddress != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_ADDRESS, mAddress));
+        }
+        if (mCountryRegion != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(
+                            doc, XmlUtils.OD_NAME_COUNTRY_REGION, mCountryRegion));
+        }
+        if (mDeveloperRelationship != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdLongEle(
+                            doc,
+                            XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP,
+                            mDeveloperRelationship.getValue()));
+        }
+        if (mWebsite != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, mWebsite));
+        }
+        if (mAppDeveloperRegistryId != null) {
+            developerInfoEle.appendChild(
+                    XmlUtils.createOdStringEle(
+                            doc,
+                            XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID,
+                            mAppDeveloperRegistryId));
+        }
+
+        return XmlUtils.listOf(developerInfoEle);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java
new file mode 100644
index 000000000000..4961892b10c3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInfo> {
+
+    /** Creates a {@link DeveloperInfo} from the human-readable DOM element. */
+    @Override
+    public DeveloperInfo createFromHrElements(List<Element> elements) throws MalformedXmlException {
+        Element developerInfoEle = XmlUtils.getSingleElement(elements);
+        if (developerInfoEle == null) {
+            AslgenUtil.logI("No DeveloperInfo found in hr format.");
+            return null;
+        }
+        String name = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_NAME);
+        String email = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_EMAIL);
+        String address = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_ADDRESS);
+        String countryRegion =
+                XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_COUNTRY_REGION);
+        DeveloperInfo.DeveloperRelationship developerRelationship =
+                DeveloperInfo.DeveloperRelationship.forString(
+                        XmlUtils.getStringAttr(
+                                developerInfoEle, XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP));
+        String website = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
+        String appDeveloperRegistryId =
+                XmlUtils.getStringAttr(
+                        developerInfoEle, XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, false);
+
+        return new DeveloperInfo(
+                name,
+                email,
+                address,
+                countryRegion,
+                developerRelationship,
+                website,
+                appDeveloperRegistryId);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
index f06522fc2a5c..40ef48dc5334 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
@@ -47,7 +47,11 @@ public class SafetyLabels implements AslMarshallable {
     public List<Element> toOdDomElements(Document doc) {
         Element safetyLabelsEle =
                 XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SAFETY_LABELS);
-        XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
-        return List.of(safetyLabelsEle);
+        safetyLabelsEle.appendChild(
+                XmlUtils.createOdLongEle(doc, XmlUtils.OD_NAME_VERSION, mVersion));
+        if (mDataLabels != null) {
+            XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
+        }
+        return XmlUtils.listOf(safetyLabelsEle);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
index 80b9f5783b9d..ab81b1d56033 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.asllib;
 
+import com.android.asllib.util.AslgenUtil;
 import com.android.asllib.util.MalformedXmlException;
 
 import org.w3c.dom.Element;
@@ -28,18 +29,16 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels>
     @Override
     public SafetyLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
         Element safetyLabelsEle = XmlUtils.getSingleElement(elements);
-        Long version;
-        try {
-            version = Long.parseLong(safetyLabelsEle.getAttribute(XmlUtils.HR_ATTR_VERSION));
-        } catch (Exception e) {
-            throw new IllegalArgumentException(
-                    "Malformed or missing required version in safety labels.");
+        if (safetyLabelsEle == null) {
+            AslgenUtil.logI("No SafetyLabels found in hr format.");
+            return null;
         }
+        long version = XmlUtils.tryGetVersion(safetyLabelsEle);
 
         DataLabels dataLabels =
                 new DataLabelsFactory()
                         .createFromHrElements(
-                                List.of(
+                                XmlUtils.listOf(
                                         XmlUtils.getSingleChildElement(
                                                 safetyLabelsEle, XmlUtils.HR_TAG_DATA_LABELS)));
         return new SafetyLabels(version, dataLabels);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java
new file mode 100644
index 000000000000..93d9c2b080c5
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** Safety Label representation containing zero or more {@link DataCategory} for data shared */
+public class SystemAppSafetyLabel implements AslMarshallable {
+
+    private final String mUrl;
+
+    public SystemAppSafetyLabel(String url) {
+        this.mUrl = url;
+    }
+
+    /** Returns the system app safety label URL. */
+    public String getUrl() {
+        return mUrl;
+    }
+
+    /** Creates an on-device DOM element from the {@link SystemAppSafetyLabel}. */
+    @Override
+    public List<Element> toOdDomElements(Document doc) {
+        Element systemAppSafetyLabelEle =
+                XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL);
+        systemAppSafetyLabelEle.appendChild(
+                XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
+        return XmlUtils.listOf(systemAppSafetyLabelEle);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java
new file mode 100644
index 000000000000..c8c1c7beba24
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<SystemAppSafetyLabel> {
+
+    /** Creates a {@link SystemAppSafetyLabel} from the human-readable DOM element. */
+    @Override
+    public SystemAppSafetyLabel createFromHrElements(List<Element> elements)
+            throws MalformedXmlException {
+        Element systemAppSafetyLabelEle = XmlUtils.getSingleElement(elements);
+        if (systemAppSafetyLabelEle == null) {
+            AslgenUtil.logI("No SystemAppSafetyLabel found in hr format.");
+            return null;
+        }
+
+        String url = XmlUtils.getStringAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_URL);
+        return new SystemAppSafetyLabel(url);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java
new file mode 100644
index 000000000000..88717b9568b8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** TransparencyInfo representation containing {@link DeveloperInfo} and {@link AppInfo} */
+public class TransparencyInfo implements AslMarshallable {
+
+    private final DeveloperInfo mDeveloperInfo;
+    private final AppInfo mAppInfo;
+
+    public TransparencyInfo(DeveloperInfo developerInfo, AppInfo appInfo) {
+        this.mDeveloperInfo = developerInfo;
+        this.mAppInfo = appInfo;
+    }
+
+    /** Gets the {@link DeveloperInfo} of the {@link TransparencyInfo}. */
+    public DeveloperInfo getDeveloperInfo() {
+        return mDeveloperInfo;
+    }
+
+    /** Gets the {@link AppInfo} of the {@link TransparencyInfo}. */
+    public AppInfo getAppInfo() {
+        return mAppInfo;
+    }
+
+    /** Creates an on-device DOM element from the {@link TransparencyInfo}. */
+    @Override
+    public List<Element> toOdDomElements(Document doc) {
+        Element transparencyInfoEle =
+                XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_TRANSPARENCY_INFO);
+        if (mDeveloperInfo != null) {
+            XmlUtils.appendChildren(transparencyInfoEle, mDeveloperInfo.toOdDomElements(doc));
+        }
+        if (mAppInfo != null) {
+            XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toOdDomElements(doc));
+        }
+        return XmlUtils.listOf(transparencyInfoEle);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java
new file mode 100644
index 000000000000..13a7eb62fedd
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class TransparencyInfoFactory implements AslMarshallableFactory<TransparencyInfo> {
+
+    /** Creates a {@link TransparencyInfo} from the human-readable DOM element. */
+    @Override
+    public TransparencyInfo createFromHrElements(List<Element> elements)
+            throws MalformedXmlException {
+        Element transparencyInfoEle = XmlUtils.getSingleElement(elements);
+        if (transparencyInfoEle == null) {
+            AslgenUtil.logI("No TransparencyInfo found in hr format.");
+            return null;
+        }
+
+        Element developerInfoEle =
+                XmlUtils.getSingleChildElement(
+                        transparencyInfoEle, XmlUtils.HR_TAG_DEVELOPER_INFO, false);
+        DeveloperInfo developerInfo =
+                new DeveloperInfoFactory().createFromHrElements(XmlUtils.listOf(developerInfoEle));
+
+        Element appInfoEle =
+                XmlUtils.getSingleChildElement(
+                        transparencyInfoEle, XmlUtils.HR_TAG_APP_INFO, false);
+        AppInfo appInfo = new AppInfoFactory().createFromHrElements(XmlUtils.listOf(appInfoEle));
+
+        return new TransparencyInfo(developerInfo, appInfo);
+    }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
index cedaf9374cd4..cc8fe79cb579 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
@@ -23,16 +23,27 @@ import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 public class XmlUtils {
     public static final String HR_TAG_APP_METADATA_BUNDLES = "app-metadata-bundles";
+    public static final String HR_TAG_SYSTEM_APP_SAFETY_LABEL = "system-app-safety-label";
     public static final String HR_TAG_SAFETY_LABELS = "safety-labels";
+    public static final String HR_TAG_TRANSPARENCY_INFO = "transparency-info";
+    public static final String HR_TAG_DEVELOPER_INFO = "developer-info";
+    public static final String HR_TAG_APP_INFO = "app-info";
     public static final String HR_TAG_DATA_LABELS = "data-labels";
     public static final String HR_TAG_DATA_ACCESSED = "data-accessed";
     public static final String HR_TAG_DATA_COLLECTED = "data-collected";
     public static final String HR_TAG_DATA_SHARED = "data-shared";
-
+    public static final String HR_ATTR_NAME = "name";
+    public static final String HR_ATTR_EMAIL = "email";
+    public static final String HR_ATTR_ADDRESS = "address";
+    public static final String HR_ATTR_COUNTRY_REGION = "countryRegion";
+    public static final String HR_ATTR_DEVELOPER_RELATIONSHIP = "relationship";
+    public static final String HR_ATTR_WEBSITE = "website";
+    public static final String HR_ATTR_APP_DEVELOPER_REGISTRY_ID = "registryId";
     public static final String HR_ATTR_DATA_CATEGORY = "dataCategory";
     public static final String HR_ATTR_DATA_TYPE = "dataType";
     public static final String HR_ATTR_IS_COLLECTION_OPTIONAL = "isCollectionOptional";
@@ -40,16 +51,55 @@ public class XmlUtils {
     public static final String HR_ATTR_EPHEMERAL = "ephemeral";
     public static final String HR_ATTR_PURPOSES = "purposes";
     public static final String HR_ATTR_VERSION = "version";
+    public static final String HR_ATTR_URL = "url";
+    public static final String HR_ATTR_TITLE = "title";
+    public static final String HR_ATTR_DESCRIPTION = "description";
+    public static final String HR_ATTR_CONTAINS_ADS = "containsAds";
+    public static final String HR_ATTR_OBEY_APS = "obeyAps";
+    public static final String HR_ATTR_ADS_FINGERPRINTING = "adsFingerprinting";
+    public static final String HR_ATTR_SECURITY_FINGERPRINTING = "securityFingerprinting";
+    public static final String HR_ATTR_PRIVACY_POLICY = "privacyPolicy";
+    public static final String HR_ATTR_SECURITY_ENDPOINTS = "securityEndpoints";
+    public static final String HR_ATTR_FIRST_PARTY_ENDPOINTS = "firstPartyEndpoints";
+    public static final String HR_ATTR_SERVICE_PROVIDER_ENDPOINTS = "serviceProviderEndpoints";
+    public static final String HR_ATTR_CATEGORY = "category";
 
     public static final String OD_TAG_BUNDLE = "bundle";
     public static final String OD_TAG_PBUNDLE_AS_MAP = "pbundle_as_map";
     public static final String OD_TAG_BOOLEAN = "boolean";
+    public static final String OD_TAG_LONG = "long";
+    public static final String OD_TAG_STRING = "string";
     public static final String OD_TAG_INT_ARRAY = "int-array";
+    public static final String OD_TAG_STRING_ARRAY = "string-array";
     public static final String OD_TAG_ITEM = "item";
     public static final String OD_ATTR_NAME = "name";
     public static final String OD_ATTR_VALUE = "value";
     public static final String OD_ATTR_NUM = "num";
     public static final String OD_NAME_SAFETY_LABELS = "safety_labels";
+    public static final String OD_NAME_TRANSPARENCY_INFO = "transparency_info";
+    public static final String OD_NAME_DEVELOPER_INFO = "developer_info";
+    public static final String OD_NAME_NAME = "name";
+    public static final String OD_NAME_EMAIL = "email";
+    public static final String OD_NAME_ADDRESS = "address";
+    public static final String OD_NAME_COUNTRY_REGION = "country_region";
+    public static final String OD_NAME_DEVELOPER_RELATIONSHIP = "relationship";
+    public static final String OD_NAME_WEBSITE = "website";
+    public static final String OD_NAME_APP_DEVELOPER_REGISTRY_ID = "app_developer_registry_id";
+    public static final String OD_NAME_APP_INFO = "app_info";
+    public static final String OD_NAME_TITLE = "title";
+    public static final String OD_NAME_DESCRIPTION = "description";
+    public static final String OD_NAME_CONTAINS_ADS = "contains_ads";
+    public static final String OD_NAME_OBEY_APS = "obey_aps";
+    public static final String OD_NAME_ADS_FINGERPRINTING = "ads_fingerprinting";
+    public static final String OD_NAME_SECURITY_FINGERPRINTING = "security_fingerprinting";
+    public static final String OD_NAME_PRIVACY_POLICY = "privacy_policy";
+    public static final String OD_NAME_SECURITY_ENDPOINT = "security_endpoint";
+    public static final String OD_NAME_FIRST_PARTY_ENDPOINT = "first_party_endpoint";
+    public static final String OD_NAME_SERVICE_PROVIDER_ENDPOINT = "service_provider_endpoint";
+    public static final String OD_NAME_CATEGORY = "category";
+    public static final String OD_NAME_VERSION = "version";
+    public static final String OD_NAME_URL = "url";
+    public static final String OD_NAME_SYSTEM_APP_SAFETY_LABEL = "system_app_safety_label";
     public static final String OD_NAME_DATA_LABELS = "data_labels";
     public static final String OD_NAME_DATA_ACCESSED = "data_accessed";
     public static final String OD_NAME_DATA_COLLECTED = "data_collected";
@@ -75,17 +125,39 @@ public class XmlUtils {
     public static Element getSingleChildElement(Element parentEle, String tagName)
             throws MalformedXmlException {
         var elements = parentEle.getElementsByTagName(tagName);
-        return getSingleElement(elements, tagName);
+        return getSingleElement(elements, tagName, true);
+    }
+
+    /**
+     * 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 {
-        if (elements.getLength() != 1) {
+        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) {
             throw new MalformedXmlException(
                     String.format(
                             "Expected 1 element \"%s\" in NodeList but got %s.",
                             tagName, elements.getLength()));
+        } else if (elements.getLength() == 0) {
+            if (required) {
+                throw new MalformedXmlException(
+                        String.format("Found no element \"%s\" in NodeList.", tagName));
+            } else {
+                return null;
+            }
         }
         var elementAsNode = elements.item(0);
         if (!(elementAsNode instanceof Element)) {
@@ -124,7 +196,7 @@ public class XmlUtils {
     }
 
     /** Gets the Boolean from the String value. */
-    public static Boolean fromString(String s) {
+    private static Boolean fromString(String s) {
         if (s == null) {
             return null;
         }
@@ -151,8 +223,86 @@ public class XmlUtils {
         return ele;
     }
 
+    /** 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);
+        ele.setAttribute(XmlUtils.OD_ATTR_NAME, name);
+        ele.setAttribute(XmlUtils.OD_ATTR_VALUE, String.valueOf(l));
+        return ele;
+    }
+
+    /** Create an on-device Long DOM Element with the given attribute name. */
+    public static Element createOdStringEle(Document doc, String name, String val) {
+        var ele = doc.createElement(XmlUtils.OD_TAG_STRING);
+        ele.setAttribute(XmlUtils.OD_ATTR_NAME, name);
+        ele.setAttribute(XmlUtils.OD_ATTR_VALUE, val);
+        return ele;
+    }
+
+    /** Create OD style array DOM Element, which can represent any time but is stored as Strings. */
+    public static Element createOdArray(
+            Document doc, String arrayTag, String arrayName, List<String> arrayVals) {
+        Element arrEle = doc.createElement(arrayTag);
+        arrEle.setAttribute(XmlUtils.OD_ATTR_NAME, arrayName);
+        arrEle.setAttribute(XmlUtils.OD_ATTR_NUM, String.valueOf(arrayVals.size()));
+        for (String s : arrayVals) {
+            Element itemEle = doc.createElement(XmlUtils.OD_TAG_ITEM);
+            itemEle.setAttribute(XmlUtils.OD_ATTR_VALUE, s);
+            arrEle.appendChild(itemEle);
+        }
+        return arrEle;
+    }
+
     /** Returns whether the String is null or empty. */
     public static boolean isNullOrEmpty(String s) {
         return s == null || s.isEmpty();
     }
+
+    /** Tries getting required version attribute and throws exception if it doesn't exist */
+    public static Long tryGetVersion(Element ele) {
+        long version;
+        try {
+            version = Long.parseLong(ele.getAttribute(XmlUtils.HR_ATTR_VERSION));
+        } catch (Exception e) {
+            throw new IllegalArgumentException(
+                    String.format(
+                            "Malformed or missing required version in: %s", ele.getTagName()));
+        }
+        return version;
+    }
+
+    /** Gets an optional Boolean attribute. */
+    public static Boolean getBoolAttr(Element ele, String attrName) {
+        return XmlUtils.fromString(ele.getAttribute(attrName));
+    }
+
+    /** Gets a required String attribute. */
+    public static String getStringAttr(Element ele, String attrName) throws MalformedXmlException {
+        return getStringAttr(ele, attrName, true);
+    }
+
+    /** Gets a String attribute; throws exception if required and non-existent. */
+    public static String getStringAttr(Element ele, String attrName, boolean required)
+            throws MalformedXmlException {
+        String s = ele.getAttribute(attrName);
+        if (isNullOrEmpty(s)) {
+            if (required) {
+                throw new MalformedXmlException(
+                        String.format(
+                                "Malformed or missing required %s in: %s",
+                                attrName, ele.getTagName()));
+            } else {
+                return null;
+            }
+        }
+        return s;
+    }
+
+    /**
+     * Utility method for making a List from one element, to support easier refactoring if needed.
+     * For example, List.of() doesn't support null elements.
+     */
+    public static List<Element> listOf(Element e) {
+        return Arrays.asList(e);
+    }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/AslgenUtil.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/AslgenUtil.java
new file mode 100644
index 000000000000..7d5421545091
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/AslgenUtil.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.util;
+
+public class AslgenUtil {
+    private static final String ASLGEN_TAG = "ASLGEN";
+
+    /** Log info. */
+    public static void logI(String s) {
+        System.out.println(String.format("%s -- INFO: %s", ASLGEN_TAG, s));
+    }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java
index cea22a2178b7..3026f8bec2ed 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java
@@ -19,6 +19,7 @@ package com.android.aslgen;
 import static org.junit.Assert.assertEquals;
 
 import com.android.asllib.AndroidSafetyLabel;
+import com.android.asllib.AslConverter;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -73,9 +74,8 @@ public class AslgenTests {
                     getClass().getClassLoader().getResourceAsStream(odPath.toString());
             String odContents = new String(odStream.readAllBytes(), StandardCharsets.UTF_8);
             AndroidSafetyLabel asl =
-                    AndroidSafetyLabel.readFromString(
-                            hrContents, AndroidSafetyLabel.Format.HUMAN_READABLE);
-            String out = asl.getXmlAsString(AndroidSafetyLabel.Format.ON_DEVICE);
+                    AslConverter.readFromString(hrContents, AslConverter.Format.HUMAN_READABLE);
+            String out = AslConverter.getXmlAsString(asl, AslConverter.Format.ON_DEVICE);
             System.out.println("out: " + out);
 
             assertEquals(getFormattedXml(out), getFormattedXml(odContents));
-- 
GitLab