Merge "ASL command-line tool initial implementation" into main
diff --git a/tools/app_metadata_bundles/Android.bp b/tools/app_metadata_bundles/Android.bp
index 5f7589d..be6bea6 100644
--- a/tools/app_metadata_bundles/Android.bp
+++ b/tools/app_metadata_bundles/Android.bp
@@ -12,11 +12,6 @@
srcs: [
"src/lib/java/**/*.java",
],
- target: {
- windows: {
- enabled: true,
- },
- },
}
java_binary_host {
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 bf906ee..df003b6 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
@@ -19,16 +19,20 @@
import com.android.asllib.AndroidSafetyLabel;
import com.android.asllib.AndroidSafetyLabel.Format;
+import org.xml.sax.SAXException;
+
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
public class Main {
- /**
- * Takes the options to make file conversion.
- */
- public static void main(String[] args) throws IOException {
+ /** Takes the options to make file conversion. */
+ public static void main(String[] args)
+ throws IOException, ParserConfigurationException, SAXException, TransformerException {
String inFile = null;
String outFile = null;
@@ -78,15 +82,13 @@
throw new IllegalArgumentException("output format is required");
}
-
System.out.println("in path: " + inFile);
System.out.println("out path: " + outFile);
System.out.println("in format: " + inFormat);
System.out.println("out format: " + outFormat);
- var asl = AndroidSafetyLabel.readFromStream(new FileInputStream(inFile),
- Format.HUMAN_READABLE);
- asl.writeToStream(new FileOutputStream(outFile), Format.ON_DEVICE);
+ var asl = AndroidSafetyLabel.readFromStream(new FileInputStream(inFile), inFormat);
+ asl.writeToStream(new FileOutputStream(outFile), outFormat);
}
private static Format getFormat(String argValue) {
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 0d13a0f..07e0e73 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,13 +16,22 @@
package com.android.asllib;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
-import java.io.OutputStreamWriter;
+
+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 {
@@ -30,30 +39,64 @@
NULL, HUMAN_READABLE, ON_DEVICE;
}
- /**
- * Reads a {@link AndroidSafetyLabel} from an {@link InputStream}.
- */
- public static AndroidSafetyLabel readFromStream(InputStream in, Format format)
- throws IOException {
- System.out.println(format);
- var br = new BufferedReader(new InputStreamReader(in));
- String line;
- while ((line = br.readLine()) != null) {
- System.out.println(line);
- }
- return new AndroidSafetyLabel();
+ private final SafetyLabels mSafetyLabels;
+
+ public SafetyLabels getSafetyLabels() {
+ return mSafetyLabels;
}
- /**
- * Write the content of the {@link AndroidSafetyLabel} to a {@link OutputStream}.
- */
- public void writeToStream(OutputStream out, Format format) throws IOException {
- var bw = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
- bw.write("Just a Test");
- bw.close();
+ private 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.
+ 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);
+ }
+
+ /** Write the content of the {@link AndroidSafetyLabel} to a {@link OutputStream}. */
+ // TODO(b/329902686): Support conversion in both directions, specified by 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));
+
+ 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);
+ }
+
+ /** 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) {
+ Element aslEle = doc.createElement(XmlUtils.OD_TAG_BUNDLE);
+ aslEle.appendChild(mSafetyLabels.toOdDomElement(doc));
+ return aslEle;
}
public static void test() {
- System.out.println("test lib");
+ // TODO(b/329902686): Add tests.
}
}
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 35ec68d..efdaa40 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
@@ -35,4 +35,9 @@
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);
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java
index 1925c28..b364c8b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java
@@ -23,7 +23,7 @@
import java.util.Set;
/**
- * Constants for determining valid {@link String} data types for usage within {@link SafetyLabel},
+ * Constants for determining valid {@link String} data types for usage within {@link SafetyLabels},
* {@link DataCategory}, and {@link DataType}
*/
public class DataCategoryConstants {
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabel.java
deleted file mode 100644
index dc8f5cc2..0000000
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabel.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 java.util.Map;
-
-/**
- * Data label representation with data shared and data collected maps containing zero or more
- * {@link DataCategory}
- */
-public class DataLabel {
- private final Map<String, DataCategory> mDataAccessed;
- private final Map<String, DataCategory> mDataCollected;
- private final Map<String, DataCategory> mDataShared;
-
- public DataLabel(
- Map<String, DataCategory> dataAccessed,
- Map<String, DataCategory> dataCollected,
- Map<String, DataCategory> dataShared) {
- mDataAccessed = dataAccessed;
- mDataCollected = dataCollected;
- mDataShared = dataShared;
- }
-
- /**
- * Returns the data accessed {@link Map} of {@link
- * com.android.asllib.DataCategoryConstants} to {@link DataCategory}
- */
- public Map<String, DataCategory> getDataAccessed() {
- return mDataAccessed;
- }
-
- /**
- * Returns the data collected {@link Map} of {@link
- * com.android.asllib.DataCategoryConstants} to {@link DataCategory}
- */
- public Map<String, DataCategory> getDataCollected() {
- return mDataCollected;
- }
-
- /**
- * Returns the data shared {@link Map} of {@link
- * com.android.asllib.DataCategoryConstants} to {@link DataCategory}
- */
- public Map<String, DataCategory> getDataShared() {
- return mDataShared;
- }
-}
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
new file mode 100644
index 0000000..d2c3d75b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
@@ -0,0 +1,176 @@
+/*
+ * 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 org.w3c.dom.NodeList;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Data label representation with data shared and data collected maps containing zero or more {@link
+ * DataCategory}
+ */
+public class DataLabels {
+ private final Map<String, DataCategory> mDataAccessed;
+ private final Map<String, DataCategory> mDataCollected;
+ private final Map<String, DataCategory> mDataShared;
+
+ public DataLabels(
+ Map<String, DataCategory> dataAccessed,
+ Map<String, DataCategory> dataCollected,
+ Map<String, DataCategory> dataShared) {
+ mDataAccessed = dataAccessed;
+ mDataCollected = dataCollected;
+ mDataShared = dataShared;
+ }
+
+ /**
+ * Returns the data accessed {@link Map} of {@link com.android.asllib.DataCategoryConstants} to
+ * {@link DataCategory}
+ */
+ public Map<String, DataCategory> getDataAccessed() {
+ return mDataAccessed;
+ }
+
+ /**
+ * Returns the data collected {@link Map} of {@link com.android.asllib.DataCategoryConstants} to
+ * {@link DataCategory}
+ */
+ public Map<String, DataCategory> getDataCollected() {
+ return mDataCollected;
+ }
+
+ /**
+ * Returns the data shared {@link Map} of {@link com.android.asllib.DataCategoryConstants} to
+ * {@link DataCategory}
+ */
+ public Map<String, DataCategory> getDataShared() {
+ 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) {
+ Element dataLabelsEle =
+ XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DATA_LABELS);
+
+ maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_ACCESSED);
+ maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED);
+ maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED);
+
+ return dataLabelsEle;
+ }
+
+ private void maybeAppendDataUsages(
+ Document doc,
+ Element dataLabelsEle,
+ Map<String, DataCategory> dataCategoriesMap,
+ String dataUsageTypeName) {
+ if (dataCategoriesMap.isEmpty()) {
+ return;
+ }
+ Element dataUsageEle = XmlUtils.createPbundleEleWithName(doc, dataUsageTypeName);
+
+ for (String dataCategoryName : dataCategoriesMap.keySet()) {
+ Element dataCategoryEle = XmlUtils.createPbundleEleWithName(doc, dataCategoryName);
+ 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);
+ }
+ 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/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
index 1601d15..7451c69 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,21 +16,75 @@
package com.android.asllib;
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
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 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 final Set<Integer> mPurposeSet;
+ private static final String PURPOSE_PREFIX = "PURPOSE_";
+
+ private final int mValue;
+
+ Purpose(int value) {
+ this.mValue = value;
+ }
+
+ /** Get the int value associated with the Purpose. */
+ public int getValue() {
+ return mValue;
+ }
+
+ /** Get the Purpose associated with the int value. */
+ public static Purpose forValue(int value) {
+ for (Purpose e : values()) {
+ if (e.getValue() == value) {
+ return e;
+ }
+ }
+ throw new IllegalArgumentException("No enum for value: " + value);
+ }
+
+ /** Get the Purpose associated with the human-readable String. */
+ public static Purpose forString(String s) {
+ for (Purpose e : values()) {
+ if (e.toString().equals(s)) {
+ return e;
+ }
+ }
+ throw new IllegalArgumentException("No 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();
+ }
+ }
+
+ private final Set<Purpose> mPurposeSet;
private final Boolean mIsCollectionOptional;
private final Boolean mIsSharingOptional;
private final Boolean mEphemeral;
private DataType(
- Set<Integer> purposeSet,
+ Set<Purpose> purposeSet,
Boolean isCollectionOptional,
Boolean isSharingOptional,
Boolean ephemeral) {
@@ -44,7 +98,7 @@
* Returns {@link Set} of valid {@link Integer} purposes for using the associated data category
* and type
*/
- public Set<Integer> getPurposeSet() {
+ public Set<Purpose> getPurposeSet() {
return mPurposeSet;
}
@@ -71,5 +125,21 @@
public Boolean getEphemeral() {
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);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java
new file mode 100644
index 0000000..a0a7537
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java
@@ -0,0 +1,156 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Constants for determining valid {@link String} data types for usage within {@link SafetyLabels},
+ * {@link DataCategory}, and {@link DataType}
+ */
+public class DataTypeConstants {
+ /** Data types for {@link DataCategoryConstants.CATEGORY_PERSONAL} */
+ public static final String TYPE_NAME = "name";
+
+ public static final String TYPE_EMAIL_ADDRESS = "email_address";
+ public static final String TYPE_PHONE_NUMBER = "phone_number";
+ public static final String TYPE_RACE_ETHNICITY = "race_ethnicity";
+ public static final String TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS =
+ "political_or_religious_beliefs";
+ public static final String TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY =
+ "sexual_orientation_or_gender_identity";
+ public static final String TYPE_PERSONAL_IDENTIFIERS = "personal_identifiers";
+ public static final String TYPE_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_FINANCIAL} */
+ public static final String TYPE_CARD_BANK_ACCOUNT = "card_bank_account";
+
+ public static final String TYPE_PURCHASE_HISTORY = "purchase_history";
+ public static final String TYPE_CREDIT_SCORE = "credit_score";
+ public static final String TYPE_FINANCIAL_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_LOCATION} */
+ public static final String TYPE_APPROX_LOCATION = "approx_location";
+
+ public static final String TYPE_PRECISE_LOCATION = "precise_location";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_EMAIL_TEXT_MESSAGE} */
+ public static final String TYPE_EMAILS = "emails";
+
+ public static final String TYPE_TEXT_MESSAGES = "text_messages";
+ public static final String TYPE_EMAIL_TEXT_MESSAGE_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_PHOTO_VIDEO} */
+ public static final String TYPE_PHOTOS = "photos";
+
+ public static final String TYPE_VIDEOS = "videos";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_AUDIO} */
+ public static final String TYPE_SOUND_RECORDINGS = "sound_recordings";
+
+ public static final String TYPE_MUSIC_FILES = "music_files";
+ public static final String TYPE_AUDIO_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_STORAGE} */
+ public static final String TYPE_FILES_DOCS = "files_docs";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_HEALTH_FITNESS} */
+ public static final String TYPE_HEALTH = "health";
+
+ public static final String TYPE_FITNESS = "fitness";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_CONTACTS} */
+ public static final String TYPE_CONTACTS = "contacts";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_CALENDAR} */
+ public static final String TYPE_CALENDAR = "calendar";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_IDENTIFIERS} */
+ public static final String TYPE_IDENTIFIERS_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_APP_PERFORMANCE} */
+ public static final String TYPE_CRASH_LOGS = "crash_logs";
+
+ public static final String TYPE_PERFORMANCE_DIAGNOSTICS = "performance_diagnostics";
+ public static final String TYPE_APP_PERFORMANCE_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_ACTIONS_IN_APP} */
+ public static final String TYPE_USER_INTERACTION = "user_interaction";
+
+ public static final String TYPE_IN_APP_SEARCH_HISTORY = "in_app_search_history";
+ public static final String TYPE_INSTALLED_APPS = "installed_apps";
+ public static final String TYPE_USER_GENERATED_CONTENT = "user_generated_content";
+ public static final String TYPE_ACTIONS_IN_APP_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_SEARCH_AND_BROWSING} */
+ public static final String TYPE_WEB_BROWSING_HISTORY = "web_browsing_history";
+
+ /** Set of valid categories */
+ public static final Set<String> VALID_TYPES =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ TYPE_NAME,
+ TYPE_EMAIL_ADDRESS,
+ TYPE_PHONE_NUMBER,
+ TYPE_RACE_ETHNICITY,
+ TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS,
+ TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY,
+ TYPE_PERSONAL_IDENTIFIERS,
+ TYPE_OTHER,
+ TYPE_CARD_BANK_ACCOUNT,
+ TYPE_PURCHASE_HISTORY,
+ TYPE_CREDIT_SCORE,
+ TYPE_FINANCIAL_OTHER,
+ TYPE_APPROX_LOCATION,
+ TYPE_PRECISE_LOCATION,
+ TYPE_EMAILS,
+ TYPE_TEXT_MESSAGES,
+ TYPE_EMAIL_TEXT_MESSAGE_OTHER,
+ TYPE_PHOTOS,
+ TYPE_VIDEOS,
+ TYPE_SOUND_RECORDINGS,
+ TYPE_MUSIC_FILES,
+ TYPE_AUDIO_OTHER,
+ TYPE_FILES_DOCS,
+ TYPE_HEALTH,
+ TYPE_FITNESS,
+ TYPE_CONTACTS,
+ TYPE_CALENDAR,
+ TYPE_IDENTIFIERS_OTHER,
+ TYPE_CRASH_LOGS,
+ TYPE_PERFORMANCE_DIAGNOSTICS,
+ TYPE_APP_PERFORMANCE_OTHER,
+ TYPE_USER_INTERACTION,
+ TYPE_IN_APP_SEARCH_HISTORY,
+ TYPE_INSTALLED_APPS,
+ TYPE_USER_GENERATED_CONTENT,
+ TYPE_ACTIONS_IN_APP_OTHER,
+ TYPE_WEB_BROWSING_HISTORY)));
+
+ /** Returns {@link Set} of valid {@link String} category keys */
+ public static Set<String> getValidDataTypes() {
+ return VALID_TYPES;
+ }
+
+ private DataTypeConstants() {
+ /* do nothing - hide constructor */
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabel.java
deleted file mode 100644
index 8d8f0bb..0000000
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabel.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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;
-
-
-/** Safety Label representation containing zero or more {@link DataCategory} for data shared */
-public class SafetyLabel {
-
- private final long mVersion;
- private final DataLabel mDataLabel;
-
- private SafetyLabel(long version, DataLabel dataLabel) {
- this.mVersion = version;
- this.mDataLabel = dataLabel;
- }
-
- /** Returns the data label for the safety label */
- public DataLabel getDataLabel() {
- return mDataLabel;
- }
-
- public long getVersion() {
- return mVersion;
- }
-}
-
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
new file mode 100644
index 0000000..6ba15e1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+/** Safety Label representation containing zero or more {@link DataCategory} for data shared */
+public class SafetyLabels {
+
+ private final Long mVersion;
+ private final DataLabels mDataLabels;
+
+ private SafetyLabels(Long version, DataLabels dataLabels) {
+ this.mVersion = version;
+ this.mDataLabels = dataLabels;
+ }
+
+ /** Returns the data label for the safety label */
+ public DataLabels getDataLabel() {
+ return mDataLabels;
+ }
+
+ /** Gets the version of the {@link SafetyLabels}. */
+ public Long getVersion() {
+ 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) {
+ Element safetyLabelsEle =
+ XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SAFETY_LABELS);
+ safetyLabelsEle.appendChild(mDataLabels.toOdDomElement(doc));
+ return safetyLabelsEle;
+ }
+}
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
new file mode 100644
index 0000000..4392c2c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
@@ -0,0 +1,119 @@
+/*
+ * 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 org.w3c.dom.NodeList;
+
+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";
+ 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_DATA_CATEGORY = "dataCategory";
+ public static final String HR_ATTR_DATA_TYPE = "dataType";
+ public static final String HR_ATTR_IS_COLLECTION_OPTIONAL = "isCollectionOptional";
+ public static final String HR_ATTR_IS_SHARING_OPTIONAL = "isSharingOptional";
+ 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 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_INT_ARRAY = "int-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_DATA_LABELS = "data_labels";
+ public static final String OD_NAME_DATA_ACCESSED = "data_accessed";
+ public static final String OD_NAME_DATA_COLLECTED = "data_collected";
+ public static final String OD_NAME_DATA_SHARED = "data_shared";
+ public static final String OD_NAME_PURPOSES = "purposes";
+ public static final String OD_NAME_IS_COLLECTION_OPTIONAL = "is_collection_optional";
+ public static final String OD_NAME_IS_SHARING_OPTIONAL = "is_sharing_optional";
+ public static final String OD_NAME_EPHEMERAL = "ephemeral";
+
+ public static final String TRUE_STR = "true";
+ public static final String FALSE_STR = "false";
+
+ /** Gets the single top-level {@link Element} having the {@param tagName}. */
+ public static Element getSingleElement(Document doc, String tagName) {
+ var elements = doc.getElementsByTagName(tagName);
+ return getSingleElement(elements, tagName);
+ }
+
+ /**
+ * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
+ */
+ public static Element getSingleElement(Element parentEle, String tagName) {
+ var elements = parentEle.getElementsByTagName(tagName);
+ return getSingleElement(elements, tagName);
+ }
+
+ /** Gets the single {@link Element} from {@param elements} and having the {@param tagName}. */
+ public static Element getSingleElement(NodeList elements, String tagName) {
+ if (elements.getLength() != 1) {
+ throw new IllegalArgumentException(
+ String.format("Expected 1 %s but got %s.", tagName, elements.getLength()));
+ }
+ var elementAsNode = elements.item(0);
+ if (!(elementAsNode instanceof Element)) {
+ throw new IllegalStateException(String.format("%s was not an element.", tagName));
+ }
+ return ((Element) elementAsNode);
+ }
+
+ /** Gets the Boolean from the String value. */
+ public static Boolean fromString(String s) {
+ if (s == null) {
+ return null;
+ }
+ if (s.equals(TRUE_STR)) {
+ return true;
+ } else if (s.equals(FALSE_STR)) {
+ return false;
+ }
+ return null;
+ }
+
+ /** Creates an on-device PBundle DOM Element with the given attribute name. */
+ public static Element createPbundleEleWithName(Document doc, String name) {
+ var ele = doc.createElement(XmlUtils.OD_TAG_PBUNDLE_AS_MAP);
+ ele.setAttribute(XmlUtils.OD_ATTR_NAME, name);
+ return ele;
+ }
+
+ /** Create an on-device Boolean DOM Element with the given attribute name. */
+ public static Element createOdBooleanEle(Document doc, String name, boolean b) {
+ var ele = doc.createElement(XmlUtils.OD_TAG_BOOLEAN);
+ ele.setAttribute(XmlUtils.OD_ATTR_NAME, name);
+ ele.setAttribute(XmlUtils.OD_ATTR_VALUE, String.valueOf(b));
+ return ele;
+ }
+
+ /** Returns whether the String is null or empty. */
+ public static boolean isNullOrEmpty(String s) {
+ return s == null || s.isEmpty();
+ }
+}