diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java index d41a428355eac..e91e04ec9cd10 100644 --- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java +++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java @@ -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 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}. + * + *

The provided parser should be pointing to a start of a valid vibration XML (i.e. to a + * start tag). No other parser position, including start of document, is considered + * valid. + * + *

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 + * 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() { } } diff --git a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java index c0fdf9f2d5275..b31af89e345ba 100644 --- a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java +++ b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java @@ -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 = "" + + "" + + "" + + ""; + VibrationEffect effect2 = VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356) + .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7) + .compose(); + String xml2 = "" + + "" + + "" + + ""; + + 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("" + xml + ""); + // Move the parser once to point to the " tag. + parser.next(); + assertParseTagSucceeds(parser, effect); + parser.next(); + assertEndTag(parser, "next-tag"); + + parser = createXmlPullParser(xml + ""); + 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( + "random text"); + assertParseTagFails(""); + assertParseTagFails(""); + assertParseTagFails(""); + } + @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();