From 6175aa77bffc0a26846bffdb4bf0610323f598fc Mon Sep 17 00:00:00 2001 From: Max Loh <mloh@google.com> Date: Thu, 28 Mar 2024 18:22:19 +0000 Subject: [PATCH] Revert^2 "Adding more supported fields/logic to aslgen." 45bd8e28c95d50cff278cd6aa775a114ea0bcc95 Bug: 329902686 Test: Unit tests. Change-Id: Iea0e25e5ef9661d9674f8c13b78898de5db10d20 --- .../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