Merge "Vibration parser for open TypedXmlPullParser" into main

This commit is contained in:
Yeabkal Wubshit
2023-07-18 18:14:48 +00:00
committed by Android (Google) Code Review
2 changed files with 151 additions and 10 deletions

View File

@@ -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() {
}
}

View File

@@ -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();