Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d4c7fa82 authored by Max Loh's avatar Max Loh
Browse files

Adding more supported fields/logic to aslgen.

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

Bug: 329902686
Test: A future CL will add a lot of unit tests, after which unit tests can be written in tandem with implementation.
Change-Id: If4fb73cdcc40abac77ec82e436ca11ebe4feb48d
parent 5b444b94
Loading
Loading
Loading
Loading
+13 −15
Original line number Diff line number Diff line
@@ -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]");
    }
}
+22 −100
Original line number Diff line number Diff line
@@ -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);
        aslEle.appendChild(XmlUtils.createOdLongEle(doc, XmlUtils.OD_NAME_VERSION, mVersion));
        if (mSafetyLabels != null) {
            XmlUtils.appendChildren(aslEle, mSafetyLabels.toOdDomElements(doc));
        return List.of(aslEle);
        }

    public static void test() {
        // TODO(b/329902686): Add tests.
        if (mSystemAppSafetyLabel != null) {
            XmlUtils.appendChildren(aslEle, mSystemAppSafetyLabel.toOdDomElements(doc));
        }
        if (mTransparencyInfo != null) {
            XmlUtils.appendChildren(aslEle, mTransparencyInfo.toOdDomElements(doc));
        }
        return XmlUtils.listOf(aslEle);
    }
}
+21 −3
Original line number Diff line number Diff line
@@ -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);
    }
}
+143 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+84 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
Loading