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 07e0e7319f4d5e9e914651594ee72beb380eca68..0f7ce689406394341b9ee3087c2b6c07c7d4d817 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 @@ -23,6 +23,7 @@ import org.xml.sax.SAXException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.List; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -33,7 +34,7 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -public class AndroidSafetyLabel { +public class AndroidSafetyLabel implements AslMarshallable { public enum Format { NULL, HUMAN_READABLE, ON_DEVICE; @@ -45,31 +46,55 @@ public class AndroidSafetyLabel { return mSafetyLabels; } - private AndroidSafetyLabel(SafetyLabels safetyLabels) { + public AndroidSafetyLabel(SafetyLabels safetyLabels) { this.mSafetyLabels = safetyLabels; } /** Reads a {@link AndroidSafetyLabel} from an {@link InputStream}. */ - // TODO(b/329902686): Support conversion in both directions, specified by format. + // TODO(b/329902686): Support parsing from on-device. public static AndroidSafetyLabel readFromStream(InputStream in, Format format) throws IOException, ParserConfigurationException, SAXException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); Document document = factory.newDocumentBuilder().parse(in); - Element appMetadataBundles = - XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES); - - return AndroidSafetyLabel.createFromHrElement(appMetadataBundles); + switch (format) { + case HUMAN_READABLE: + Element appMetadataBundles = + XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES); + + return new AndroidSafetyLabelFactory() + .createFromHrElements( + XmlUtils.asElementList( + document.getElementsByTagName( + 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."); + } } /** Write the content of the {@link AndroidSafetyLabel} to a {@link OutputStream}. */ - // TODO(b/329902686): Support conversion in both directions, specified by format. + // 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(); - document.appendChild(this.toOdDomElement(document)); + + 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(); @@ -81,19 +106,12 @@ public class AndroidSafetyLabel { transformer.transform(domSource, streamResult); } - /** Creates an {@link AndroidSafetyLabel} from human-readable DOM element */ - public static AndroidSafetyLabel createFromHrElement(Element appMetadataBundlesEle) { - Element safetyLabelsEle = - XmlUtils.getSingleElement(appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS); - SafetyLabels safetyLabels = SafetyLabels.createFromHrElement(safetyLabelsEle); - return new AndroidSafetyLabel(safetyLabels); - } - /** Creates an on-device DOM element from an {@link AndroidSafetyLabel} */ - public Element toOdDomElement(Document doc) { + @Override + public List<Element> toOdDomElements(Document doc) { Element aslEle = doc.createElement(XmlUtils.OD_TAG_BUNDLE); - aslEle.appendChild(mSafetyLabels.toOdDomElement(doc)); - return aslEle; + XmlUtils.appendChildren(aslEle, mSafetyLabels.toOdDomElements(doc)); + return List.of(aslEle); } public static void test() { 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 new file mode 100644 index 0000000000000000000000000000000000000000..9b0f05b0c633efb448dd75c9c38e9d22a10ef3d8 --- /dev/null +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java @@ -0,0 +1,36 @@ +/* + * 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.Element; + +import java.util.List; + +public class AndroidSafetyLabelFactory implements AslMarshallableFactory<AndroidSafetyLabel> { + + /** Creates an {@link AndroidSafetyLabel} from human-readable DOM element */ + @Override + public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles) { + Element appMetadataBundlesEle = XmlUtils.getSingleElement(appMetadataBundles); + Element safetyLabelsEle = + XmlUtils.getSingleChildElement( + appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS); + SafetyLabels safetyLabels = + new SafetyLabelsFactory().createFromHrElements(List.of(safetyLabelsEle)); + return new AndroidSafetyLabel(safetyLabels); + } +} diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java new file mode 100644 index 0000000000000000000000000000000000000000..4e64ab0c53c12b18af4047f94d8a59e52239af4c --- /dev/null +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java @@ -0,0 +1,28 @@ +/* + * 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; + +public interface AslMarshallable { + + /** Creates the on-device DOM element from the AslMarshallable Java Object. */ + List<Element> toOdDomElements(Document doc); +} diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..b607353791ff10f917acc2c37aab109fa06df053 --- /dev/null +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java @@ -0,0 +1,27 @@ +/* + * 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.Element; + +import java.util.List; + +public interface AslMarshallableFactory<T extends AslMarshallable> { + + /** Creates an {@link AslMarshallableFactory} from human-readable DOM element */ + T createFromHrElements(List<Element> elements); +} 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 efdaa4062bdbbf5b39b505afdbd2af8498faf0a8..e5ed63b74ebfbdcb2b2f29899985c899bf697e8c 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 @@ -16,6 +16,10 @@ package com.android.asllib; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.List; import java.util.Map; /** @@ -23,21 +27,32 @@ import java.util.Map; * are defined in {@link DataCategoryConstants}, each category has a valid set of types {@link * DataType}, which are mapped in {@link DataTypeConstants} */ -public class DataCategory { +public class DataCategory implements AslMarshallable { + private final String mCategoryName; private final Map<String, DataType> mDataTypes; - private DataCategory(Map<String, DataType> dataTypes) { + public DataCategory(String categoryName, Map<String, DataType> dataTypes) { + this.mCategoryName = categoryName; this.mDataTypes = dataTypes; } + public String getCategoryName() { + return mCategoryName; + } + /** Return the type {@link Map} of String type key to {@link DataType} */ public Map<String, DataType> getDataTypes() { return mDataTypes; } - /** Creates a {@link DataCategory} given map of {@param dataTypes}. */ - public static DataCategory create(Map<String, DataType> dataTypes) { - return new DataCategory(dataTypes); + /** Creates on-device DOM element(s) from the {@link DataCategory}. */ + @Override + public List<Element> toOdDomElements(Document doc) { + Element dataCategoryEle = XmlUtils.createPbundleEleWithName(doc, this.getCategoryName()); + for (DataType dataType : mDataTypes.values()) { + XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc)); + } + return List.of(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 new file mode 100644 index 0000000000000000000000000000000000000000..5a52591eaf8c0a1447f1d24a187a892804340ec0 --- /dev/null +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java @@ -0,0 +1,38 @@ +/* + * 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.Element; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DataCategoryFactory implements AslMarshallableFactory<DataCategory> { + @Override + public DataCategory createFromHrElements(List<Element> elements) { + String categoryName = null; + Map<String, DataType> dataTypeMap = new HashMap<String, DataType>(); + for (Element ele : elements) { + categoryName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY); + String dataTypeName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE); + dataTypeMap.put(dataTypeName, new DataTypeFactory().createFromHrElements(List.of(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 d2c3d75b1d9c0df4cf726237ca25e907ca3f10f5..d2fffc0a36f6c60a61b51e662782d321759603ce 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 @@ -18,16 +18,15 @@ package com.android.asllib; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import java.util.HashMap; +import java.util.List; import java.util.Map; /** * Data label representation with data shared and data collected maps containing zero or more {@link * DataCategory} */ -public class DataLabels { +public class DataLabels implements AslMarshallable { private final Map<String, DataCategory> mDataAccessed; private final Map<String, DataCategory> mDataCollected; private final Map<String, DataCategory> mDataShared; @@ -65,46 +64,9 @@ public class DataLabels { return mDataShared; } - /** Creates a {@link DataLabels} from the human-readable DOM element. */ - public static DataLabels createFromHrElement(Element ele) { - Map<String, DataCategory> dataAccessed = - getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED); - Map<String, DataCategory> dataCollected = - getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED); - Map<String, DataCategory> dataShared = - getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED); - return new DataLabels(dataAccessed, dataCollected, dataShared); - } - - private static Map<String, DataCategory> getDataCategoriesWithTag( - Element dataLabelsEle, String dataCategoryUsageTypeTag) { - Map<String, Map<String, DataType>> dataTypeMap = - new HashMap<String, Map<String, DataType>>(); - NodeList dataSharedNodeList = dataLabelsEle.getElementsByTagName(dataCategoryUsageTypeTag); - - for (int i = 0; i < dataSharedNodeList.getLength(); i++) { - Element dataSharedEle = (Element) dataSharedNodeList.item(i); - String dataCategoryName = dataSharedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY); - String dataTypeName = dataSharedEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE); - - if (!dataTypeMap.containsKey((dataCategoryName))) { - dataTypeMap.put(dataCategoryName, new HashMap<String, DataType>()); - } - dataTypeMap - .get(dataCategoryName) - .put(dataTypeName, DataType.createFromHrElement(dataSharedEle)); - } - - Map<String, DataCategory> dataCategoryMap = new HashMap<String, DataCategory>(); - for (String dataCategoryName : dataTypeMap.keySet()) { - Map<String, DataType> dataTypes = dataTypeMap.get(dataCategoryName); - dataCategoryMap.put(dataCategoryName, DataCategory.create(dataTypes)); - } - return dataCategoryMap; - } - /** Gets the on-device DOM element for the {@link DataLabels}. */ - public Element toOdDomElement(Document doc) { + @Override + public List<Element> toOdDomElements(Document doc) { Element dataLabelsEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DATA_LABELS); @@ -112,7 +74,7 @@ public class DataLabels { maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED); maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED); - return dataLabelsEle; + return List.of(dataLabelsEle); } private void maybeAppendDataUsages( @@ -130,47 +92,10 @@ public class DataLabels { DataCategory dataCategory = dataCategoriesMap.get(dataCategoryName); for (String dataTypeName : dataCategory.getDataTypes().keySet()) { DataType dataType = dataCategory.getDataTypes().get(dataTypeName); - Element dataTypeEle = XmlUtils.createPbundleEleWithName(doc, dataTypeName); - if (!dataType.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(dataType.getPurposeSet().size())); - for (DataType.Purpose purpose : dataType.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); - } - - maybeAddBoolToOdElement( - doc, - dataTypeEle, - dataType.getIsCollectionOptional(), - XmlUtils.OD_NAME_IS_COLLECTION_OPTIONAL); - maybeAddBoolToOdElement( - doc, - dataTypeEle, - dataType.getIsSharingOptional(), - XmlUtils.OD_NAME_IS_SHARING_OPTIONAL); - maybeAddBoolToOdElement( - doc, dataTypeEle, dataType.getEphemeral(), XmlUtils.OD_NAME_EPHEMERAL); - - dataCategoryEle.appendChild(dataTypeEle); + XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc)); } dataUsageEle.appendChild(dataCategoryEle); } dataLabelsEle.appendChild(dataUsageEle); } - - private static void maybeAddBoolToOdElement( - Document doc, Element parentEle, Boolean b, String odName) { - if (b == null) { - return; - } - Element ele = XmlUtils.createOdBooleanEle(doc, odName, b); - parentEle.appendChild(ele); - } } 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 new file mode 100644 index 0000000000000000000000000000000000000000..c758ab923bbfad88e4faef68adfff41a2352e854 --- /dev/null +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java @@ -0,0 +1,70 @@ +/* + * 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.Element; +import org.w3c.dom.NodeList; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { + + /** Creates a {@link DataLabels} from the human-readable DOM element. */ + @Override + public DataLabels createFromHrElements(List<Element> elements) { + Element ele = XmlUtils.getSingleElement(elements); + Map<String, DataCategory> dataAccessed = + getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED); + Map<String, DataCategory> dataCollected = + getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED); + Map<String, DataCategory> dataShared = + getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED); + return new DataLabels(dataAccessed, dataCollected, dataShared); + } + + private static Map<String, DataCategory> getDataCategoriesWithTag( + Element dataLabelsEle, String dataCategoryUsageTypeTag) { + Map<String, Map<String, DataType>> dataTypeMap = + new HashMap<String, Map<String, DataType>>(); + NodeList dataUsedNodeList = dataLabelsEle.getElementsByTagName(dataCategoryUsageTypeTag); + Map<String, DataCategory> dataCategoryMap = new HashMap<String, DataCategory>(); + + Set<String> dataCategoryNames = new HashSet<String>(); + for (int i = 0; i < dataUsedNodeList.getLength(); i++) { + Element dataUsedEle = (Element) dataUsedNodeList.item(i); + String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY); + dataCategoryNames.add(dataCategoryName); + } + for (String dataCategoryName : dataCategoryNames) { + var dataCategoryElements = + XmlUtils.asElementList(dataUsedNodeList).stream() + .filter( + ele -> + ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY) + .equals(dataCategoryName)) + .toList(); + DataCategory dataCategory = + new DataCategoryFactory().createFromHrElements(dataCategoryElements); + dataCategoryMap.put(dataCategoryName, dataCategory); + } + return dataCategoryMap; + } +} diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java index 7451c69231136fa61ffeb6c1d7e7f2c2ac0d5ce1..5ba29757e19e07cc345a02b12144a301f34b08c3 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 @@ -16,17 +16,18 @@ package com.android.asllib; +import org.w3c.dom.Document; import org.w3c.dom.Element; -import java.util.Arrays; +import java.util.List; import java.util.Set; -import java.util.stream.Collectors; /** * Data usage type representation. Types are specific to a {@link DataCategory} and contains * metadata related to the data usage purpose. */ -public class DataType { +public class DataType implements AslMarshallable { + public enum Purpose { PURPOSE_APP_FUNCTIONALITY(1), PURPOSE_ANALYTICS(2), @@ -78,22 +79,30 @@ public class DataType { } } + private final String mDataTypeName; + private final Set<Purpose> mPurposeSet; private final Boolean mIsCollectionOptional; private final Boolean mIsSharingOptional; private final Boolean mEphemeral; - private DataType( + public DataType( + String dataTypeName, Set<Purpose> purposeSet, Boolean isCollectionOptional, Boolean isSharingOptional, Boolean ephemeral) { + this.mDataTypeName = dataTypeName; this.mPurposeSet = purposeSet; this.mIsCollectionOptional = isCollectionOptional; this.mIsSharingOptional = isSharingOptional; this.mEphemeral = ephemeral; } + public String getDataTypeName() { + return mDataTypeName; + } + /** * Returns {@link Set} of valid {@link Integer} purposes for using the associated data category * and type @@ -126,20 +135,42 @@ public class DataType { return mEphemeral; } - /** Creates a {@link DataType} from the human-readable DOM element. */ - public static DataType createFromHrElement(Element hrDataTypeEle) { - Set<Purpose> purposeSet = - Arrays.stream(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_PURPOSES).split("\\|")) - .map(Purpose::forString) - .collect(Collectors.toUnmodifiableSet()); - Boolean isCollectionOptional = - XmlUtils.fromString( - hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL)); - Boolean isSharingOptional = - XmlUtils.fromString( - hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL)); - Boolean ephemeral = - XmlUtils.fromString(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_EPHEMERAL)); - return new DataType(purposeSet, isCollectionOptional, isSharingOptional, ephemeral); + @Override + 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); + } + + maybeAddBoolToOdElement( + doc, + dataTypeEle, + this.getIsCollectionOptional(), + XmlUtils.OD_NAME_IS_COLLECTION_OPTIONAL); + maybeAddBoolToOdElement( + doc, + dataTypeEle, + this.getIsSharingOptional(), + XmlUtils.OD_NAME_IS_SHARING_OPTIONAL); + maybeAddBoolToOdElement(doc, dataTypeEle, this.getEphemeral(), XmlUtils.OD_NAME_EPHEMERAL); + return List.of(dataTypeEle); + } + + private static void maybeAddBoolToOdElement( + Document doc, Element parentEle, Boolean b, String odName) { + if (b == null) { + return; + } + Element ele = XmlUtils.createOdBooleanEle(doc, odName, b); + parentEle.appendChild(ele); } } 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 new file mode 100644 index 0000000000000000000000000000000000000000..99f8a8b1b1521190a9e7b6ff875353ddc0a7bd44 --- /dev/null +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.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.Element; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class DataTypeFactory implements AslMarshallableFactory<DataType> { + /** Creates a {@link DataType} from the human-readable DOM element. */ + @Override + public DataType createFromHrElements(List<Element> elements) { + Element hrDataTypeEle = XmlUtils.getSingleElement(elements); + String dataTypeName = hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE); + Set<DataType.Purpose> purposeSet = + Arrays.stream(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_PURPOSES).split("\\|")) + .map(DataType.Purpose::forString) + .collect(Collectors.toUnmodifiableSet()); + Boolean isCollectionOptional = + XmlUtils.fromString( + hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL)); + Boolean isSharingOptional = + XmlUtils.fromString( + hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL)); + Boolean ephemeral = + XmlUtils.fromString(hrDataTypeEle.getAttribute(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/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java index 6ba15e1ec4db45cdc0c27cf1705284b8c57c0d0c..f06522fc2a5cdc7332ae951cf4d9e4abdc5f4792 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 @@ -19,13 +19,15 @@ 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 SafetyLabels { +public class SafetyLabels implements AslMarshallable { private final Long mVersion; private final DataLabels mDataLabels; - private SafetyLabels(Long version, DataLabels dataLabels) { + public SafetyLabels(Long version, DataLabels dataLabels) { this.mVersion = version; this.mDataLabels = dataLabels; } @@ -40,26 +42,12 @@ public class SafetyLabels { return mVersion; } - /** Creates a {@link SafetyLabels} from the human-readable DOM element. */ - public static SafetyLabels createFromHrElement(Element safetyLabelsEle) { - 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."); - } - Element dataLabelsEle = - XmlUtils.getSingleElement(safetyLabelsEle, XmlUtils.HR_TAG_DATA_LABELS); - DataLabels dataLabels = DataLabels.createFromHrElement(dataLabelsEle); - return new SafetyLabels(version, dataLabels); - } - /** Creates an on-device DOM element from the {@link SafetyLabels}. */ - public Element toOdDomElement(Document doc) { + @Override + public List<Element> toOdDomElements(Document doc) { Element safetyLabelsEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SAFETY_LABELS); - safetyLabelsEle.appendChild(mDataLabels.toOdDomElement(doc)); - return safetyLabelsEle; + XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc)); + return List.of(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 new file mode 100644 index 0000000000000000000000000000000000000000..68e83fe30db1c024c1cf7cd683ce3885d32a1872 --- /dev/null +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java @@ -0,0 +1,45 @@ +/* + * 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.Element; + +import java.util.List; + +public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> { + + /** Creates a {@link SafetyLabels} from the human-readable DOM element. */ + @Override + public SafetyLabels createFromHrElements(List<Element> elements) { + 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."); + } + + DataLabels dataLabels = + new DataLabelsFactory() + .createFromHrElements( + List.of( + 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/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java index 4392c2c220c4814d53922ac6c456757e7399b27a..3c89a308036f5ccf186aa16826f7eb49d7e0827d 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 @@ -20,6 +20,9 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; +import java.util.ArrayList; +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_SAFETY_LABELS = "safety-labels"; @@ -60,30 +63,60 @@ public class XmlUtils { /** Gets the single top-level {@link Element} having the {@param tagName}. */ public static Element getSingleElement(Document doc, String tagName) { var elements = doc.getElementsByTagName(tagName); - return getSingleElement(elements, tagName); + return getSingleElement(elements); } /** * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}. */ - public static Element getSingleElement(Element parentEle, String tagName) { + public static Element getSingleChildElement(Element parentEle, String tagName) { var elements = parentEle.getElementsByTagName(tagName); - return getSingleElement(elements, tagName); + return getSingleElement(elements); } - /** Gets the single {@link Element} from {@param elements} and having the {@param tagName}. */ - public static Element getSingleElement(NodeList elements, String tagName) { + /** Gets the single {@link Element} from {@param elements} */ + public static Element getSingleElement(NodeList elements) { if (elements.getLength() != 1) { throw new IllegalArgumentException( - String.format("Expected 1 %s but got %s.", tagName, elements.getLength())); + String.format( + "Expected 1 element in NodeList but got %s.", elements.getLength())); } var elementAsNode = elements.item(0); if (!(elementAsNode instanceof Element)) { - throw new IllegalStateException(String.format("%s was not an element.", tagName)); + throw new IllegalStateException( + String.format("%s was not an element.", elementAsNode.getNodeName())); } return ((Element) elementAsNode); } + /** Gets the single {@link Element} within {@param elements}. */ + public static Element getSingleElement(List<Element> elements) { + if (elements.size() != 1) { + throw new IllegalStateException( + String.format("Expected 1 element in list but got %s.", elements.size())); + } + return elements.get(0); + } + + /** Converts {@param nodeList} into List of {@link Element}. */ + public static List<Element> asElementList(NodeList nodeList) { + List<Element> elementList = new ArrayList<Element>(); + for (int i = 0; i < nodeList.getLength(); i++) { + var elementAsNode = nodeList.item(0); + if (elementAsNode instanceof Element) { + elementList.add(((Element) elementAsNode)); + } + } + return elementList; + } + + /** Appends {@param children} to the {@param ele}. */ + public static void appendChildren(Element ele, List<Element> children) { + for (Element c : children) { + ele.appendChild(c); + } + } + /** Gets the Boolean from the String value. */ public static Boolean fromString(String s) { if (s == null) {