Loading core/java/android/os/vibrator/persistence/VibrationXmlParser.java +46 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { } } core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java +105 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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(); Loading Loading
core/java/android/os/vibrator/persistence/VibrationXmlParser.java +46 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { } }
core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java +105 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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(); Loading