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

Commit 7cf76278 authored by Yeabkal Wubshit's avatar Yeabkal Wubshit Committed by Android (Google) Code Review
Browse files

Merge "Vibration parser for open TypedXmlPullParser" into main

parents b446c646 9388adb5
Loading
Loading
Loading
Loading
+46 −10
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import com.android.internal.vibrator.persistence.VibrationEffectXmlParser;
import com.android.internal.vibrator.persistence.XmlConstants;
import com.android.internal.vibrator.persistence.XmlParserException;
import com.android.internal.vibrator.persistence.XmlReader;
import com.android.internal.vibrator.persistence.XmlSerializedVibration;
import com.android.modules.utils.TypedXmlPullParser;

import org.xmlpull.v1.XmlPullParser;
@@ -178,25 +177,62 @@ public final class VibrationXmlParser {
            // Ensure XML starts with expected root tag.
            XmlReader.readDocumentStartTag(parser, XmlConstants.TAG_VIBRATION);

            int parserFlags = 0;
            if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) {
                parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
            }

            // Parse root tag as a vibration effect.
            XmlSerializedVibration<VibrationEffect> serializedVibration =
                    VibrationEffectXmlParser.parseTag(parser, parserFlags);
            VibrationEffect effect = parseTag(parser, flags);

            // Ensure XML ends after root tag is consumed.
            XmlReader.readDocumentEndTag(parser);

            return serializedVibration.deserialize();
        } catch (XmlParserException e) {
            return effect;
        } catch (XmlParserException | VibrationXmlParserException e) {
            Slog.w(TAG, "Error parsing vibration XML", e);
            return null;
        }
    }

    /**
     * Parses XML content from given open {@link TypedXmlPullParser} into a {@link VibrationEffect}.
     *
     * <p>The provided parser should be pointing to a start of a valid vibration XML (i.e. to a
     * start <vibration> tag). No other parser position, including start of document, is considered
     * valid.
     *
     * <p>This method parses as long as it reads a valid vibration XML, and until an end vibration
     * tag. After a successful parsing, the parser will point to the end vibration tag (i.e. to a
     * </vibration> tag).
     *
     * @throws IOException error parsing from given {@link TypedXmlPullParser}.
     * @throws VibrationXmlParserException if the XML tag cannot be parsed into a
     *      {@link VibrationEffect}. The given {@code parser} might be pointing to a child XML tag
     *      that caused the parser failure.
     *
     * @hide
     */
    @NonNull
    public static VibrationEffect parseTag(@NonNull TypedXmlPullParser parser, @Flags int flags)
            throws IOException, VibrationXmlParserException {
        int parserFlags = 0;
        if ((flags & VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS) != 0) {
            parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
        }
        try {
            return VibrationEffectXmlParser.parseTag(parser, parserFlags).deserialize();
        } catch (XmlParserException e) {
            throw new VibrationXmlParserException("Error parsing vibration effect.", e);
        }
    }

    /**
     * Represents an error while parsing a vibration XML input.
     *
     * @hide
     */
    public static final class VibrationXmlParserException extends Exception {
        private VibrationXmlParserException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    private VibrationXmlParser() {
    }
}
+105 −0
Original line number Diff line number Diff line
@@ -29,10 +29,14 @@ import static org.junit.Assert.assertThrows;
import android.os.VibrationEffect;
import android.os.vibrator.PrebakedSegment;
import android.platform.test.annotations.Presubmit;
import android.util.Xml;

import com.android.modules.utils.TypedXmlPullParser;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.xmlpull.v1.XmlPullParser;

import java.io.IOException;
import java.io.StringReader;
@@ -64,6 +68,61 @@ public class VibrationEffectXmlSerializationTest {
        assertThat(isSupportedMimeType("application/vnd.android.vibration+xml")).isFalse();
    }

    @Test
    public void testParseTag_succeedAndParserPointsToEndVibrationTag() throws Exception {
        VibrationEffect effect = VibrationEffect.startComposition()
                .addPrimitive(PRIMITIVE_CLICK)
                .addPrimitive(PRIMITIVE_TICK, 0.2497f)
                .compose();
        String xml = "<vibration>"
                + "<primitive-effect name=\"click\"/>"
                + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
                + "</vibration>";
        VibrationEffect effect2 = VibrationEffect.startComposition()
                .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
                .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
                .compose();
        String xml2 = "<vibration>"
                + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
                + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
                + "</vibration>";

        TypedXmlPullParser parser = createXmlPullParser(xml);
        assertParseTagSucceeds(parser, effect);
        parser.next();
        assertEndOfDocument(parser);

        // Test no-issues when an end-tag follows the vibration XML.
        // To test this, starting with the corresponding "start-tag" is necessary.
        parser = createXmlPullParser("<next-tag>" + xml + "</next-tag>");
        // Move the parser once to point to the "<vibration> tag.
        parser.next();
        assertParseTagSucceeds(parser, effect);
        parser.next();
        assertEndTag(parser, "next-tag");

        parser = createXmlPullParser(xml + "<next-tag>");
        assertParseTagSucceeds(parser, effect);
        parser.next();
        assertStartTag(parser, "next-tag");

        parser = createXmlPullParser(xml + xml2);
        assertParseTagSucceeds(parser, effect);
        parser.next();
        assertParseTagSucceeds(parser, effect2);
        parser.next();
        assertEndOfDocument(parser);
    }

    @Test
    public void testParseTag_badXml_throwsException() throws Exception {
        assertParseTagFails(
                "<vibration>random text<primitive-effect name=\"click\"/></vibration>");
        assertParseTagFails("<bad-tag><primitive-effect name=\"click\"/></vibration>");
        assertParseTagFails("<primitive-effect name=\"click\"/></vibration>");
        assertParseTagFails("<vibration><primitive-effect name=\"click\"/>");
    }

    @Test
    public void testPrimitives_allSucceed() throws IOException {
        VibrationEffect effect = VibrationEffect.startComposition()
@@ -172,6 +231,41 @@ public class VibrationEffectXmlSerializationTest {
        assertThat(parse(xml, /* flags= */ 0)).isEqualTo(effect);
    }

    private TypedXmlPullParser createXmlPullParser(String xml) throws Exception {
        TypedXmlPullParser parser = Xml.newFastPullParser();
        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
        parser.setInput(new StringReader(xml));
        parser.next(); // read START_DOCUMENT
        return parser;
    }

    /**
     * Asserts parsing vibration from an open TypedXmlPullParser succeeds, and that the parser
     * points to the end "vibration" tag.
     */
    private void assertParseTagSucceeds(
            TypedXmlPullParser parser, VibrationEffect effect) throws Exception {
        assertThat(parseTag(parser)).isEqualTo(effect);

        assertThat(parser.getEventType()).isEqualTo(XmlPullParser.END_TAG);
        assertThat(parser.getName()).isEqualTo("vibration");
    }

    private void assertEndTag(TypedXmlPullParser parser, String expectedTagName) throws Exception {
        assertThat(parser.getName()).isEqualTo(expectedTagName);
        assertThat(parser.getEventType()).isEqualTo(parser.END_TAG);
    }

    private void assertStartTag(TypedXmlPullParser parser, String expectedTagName)
            throws Exception {
        assertThat(parser.getName()).isEqualTo(expectedTagName);
        assertThat(parser.getEventType()).isEqualTo(parser.START_TAG);
    }

    private void assertEndOfDocument(TypedXmlPullParser parser) throws Exception {
        assertThat(parser.getEventType()).isEqualTo(parser.END_DOCUMENT);
    }

    private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect)
            throws IOException {
        assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect);
@@ -183,6 +277,12 @@ public class VibrationEffectXmlSerializationTest {
                () -> serialize(effect, /* flags= */ 0));
    }

    private void assertParseTagFails(String xml) {
        assertThrows("Expected parsing to fail for " + xml,
                VibrationXmlParser.VibrationXmlParserException.class,
                () -> parseTag(createXmlPullParser(xml)));
    }

    private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
            String... expectedSegments) throws IOException {
        assertSerializationContainsSegments(serialize(effect, /* flags= */ 0), expectedSegments);
@@ -214,6 +314,11 @@ public class VibrationEffectXmlSerializationTest {
        return VibrationXmlParser.parse(new StringReader(xml), flags);
    }

    private static VibrationEffect parseTag(TypedXmlPullParser parser)
            throws IOException, VibrationXmlParser.VibrationXmlParserException {
        return VibrationXmlParser.parseTag(parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
    }

    private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags)
            throws IOException {
        StringWriter writer = new StringWriter();