Merge "Add flags to VibrationEffect XML parser/serializer"
This commit is contained in:
committed by
Android (Google) Code Review
commit
5aedcf878f
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.os.vibrator.persistence;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
@@ -35,6 +36,8 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Parses XML into a {@link VibrationEffect}.
|
||||
@@ -43,10 +46,10 @@ import java.io.Reader;
|
||||
*
|
||||
* * Predefined vibration effects
|
||||
*
|
||||
* <pre>VibrationEffect
|
||||
* <pre>
|
||||
* {@code
|
||||
* <vibration>
|
||||
* <predefined-effect id="0" />
|
||||
* <predefined-effect name="click" />
|
||||
* </vibration>
|
||||
* }
|
||||
* </pre>
|
||||
@@ -75,10 +78,10 @@ import java.io.Reader;
|
||||
* <pre>
|
||||
* {@code
|
||||
* <vibration>
|
||||
* <primitive-effect id="1" />
|
||||
* <primitive-effect id="2" scale="0.8" />
|
||||
* <primitive-effect id="3" delayMs="50" />
|
||||
* <primitive-effect id="2" scale="0.5" delayMs="100" />
|
||||
* <primitive-effect name="click" />
|
||||
* <primitive-effect name="slow_rise" scale="0.8" />
|
||||
* <primitive-effect name="quick_fall" delayMs="50" />
|
||||
* <primitive-effect name="tick" scale="0.5" delayMs="100" />
|
||||
* </vibration>
|
||||
* }
|
||||
* </pre>
|
||||
@@ -89,6 +92,24 @@ import java.io.Reader;
|
||||
public final class VibrationXmlParser {
|
||||
private static final String TAG = "VibrationXmlParser";
|
||||
|
||||
/**
|
||||
* Allows {@link VibrationEffect} instances created via non-public APIs to be parsed/serialized.
|
||||
*
|
||||
* <p>Note that the XML schema for non-public APIs is not backwards compatible. This is intended
|
||||
* for loading custom {@link VibrationEffect} configured per device and platform version, not
|
||||
* to be restored from old platform versions.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_ALLOW_HIDDEN_APIS = 1 << 0; // Same as VibrationXmlSerializer
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "FLAG_" }, flag = true, value = {
|
||||
FLAG_ALLOW_HIDDEN_APIS
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Flags {}
|
||||
|
||||
/**
|
||||
* Parses XML content from given input stream into a {@link VibrationEffect}.
|
||||
*
|
||||
@@ -103,6 +124,19 @@ public final class VibrationXmlParser {
|
||||
@TestApi
|
||||
@Nullable
|
||||
public static VibrationEffect parse(@NonNull Reader reader) throws IOException {
|
||||
return parse(reader, /* flags= */ 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses XML content from given input stream into a {@link VibrationEffect}.
|
||||
*
|
||||
* <p>Same as {@link #parse(Reader)}, with extra flags to control the parsing behavior.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public static VibrationEffect parse(@NonNull Reader reader, @Flags int flags)
|
||||
throws IOException {
|
||||
TypedXmlPullParser parser = Xml.newFastPullParser();
|
||||
|
||||
try {
|
||||
@@ -116,14 +150,19 @@ 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> serializable =
|
||||
VibrationEffectXmlParser.parseTag(parser);
|
||||
XmlSerializedVibration<VibrationEffect> serializedVibration =
|
||||
VibrationEffectXmlParser.parseTag(parser, parserFlags);
|
||||
|
||||
// Ensure XML ends after root tag is consumed.
|
||||
XmlReader.readDocumentEndTag(parser);
|
||||
|
||||
return serializable.deserialize();
|
||||
return serializedVibration.deserialize();
|
||||
} catch (XmlParserException e) {
|
||||
Slog.w(TAG, "Error parsing vibration XML", e);
|
||||
return null;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.os.vibrator.persistence;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.TestApi;
|
||||
import android.os.CombinedVibration;
|
||||
@@ -23,6 +24,7 @@ import android.os.VibrationEffect;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.vibrator.persistence.VibrationEffectXmlSerializer;
|
||||
import com.android.internal.vibrator.persistence.XmlConstants;
|
||||
import com.android.internal.vibrator.persistence.XmlSerializedVibration;
|
||||
import com.android.internal.vibrator.persistence.XmlSerializerException;
|
||||
import com.android.internal.vibrator.persistence.XmlValidator;
|
||||
@@ -30,6 +32,8 @@ import com.android.modules.utils.TypedXmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Serializes {@link CombinedVibration} and {@link VibrationEffect} instances to XML.
|
||||
@@ -40,10 +44,37 @@ import java.io.Writer;
|
||||
*/
|
||||
@TestApi
|
||||
public final class VibrationXmlSerializer {
|
||||
private static final String TAG = "VibrationXmlSerializer";
|
||||
|
||||
private static final String SERIALIZER_ENCODING = Xml.Encoding.UTF_8.name();
|
||||
private static final String SERIALIZER_FEATURE_INDENT_OUTPUT =
|
||||
/**
|
||||
* Allows {@link VibrationEffect} instances created via non-public APIs to be parsed/serialized.
|
||||
*
|
||||
* <p>Note that the XML schema for non-public APIs is not backwards compatible. This is intended
|
||||
* for loading custom {@link VibrationEffect} configured per device and platform version, not
|
||||
* to be restored from old platform versions or from different devices.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_ALLOW_HIDDEN_APIS = 1 << 0;
|
||||
|
||||
/**
|
||||
* Writes a more human-readable output XML.
|
||||
*
|
||||
* <p>This will be less compact as it includes extra whitespace for things like indentation.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_PRETTY_PRINT = 1 << 1;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "FLAG_" }, flag = true, value = {
|
||||
FLAG_PRETTY_PRINT,
|
||||
FLAG_ALLOW_HIDDEN_APIS
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Flags {}
|
||||
|
||||
private static final String XML_ENCODING = Xml.Encoding.UTF_8.name();
|
||||
private static final String XML_FEATURE_INDENT_OUTPUT =
|
||||
"http://xmlpull.org/v1/doc/features.html#indent-output";
|
||||
|
||||
/**
|
||||
@@ -62,21 +93,47 @@ public final class VibrationXmlSerializer {
|
||||
@TestApi
|
||||
public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer)
|
||||
throws SerializationFailedException, IOException {
|
||||
XmlSerializedVibration<VibrationEffect> serializableEffect;
|
||||
serialize(effect, writer, /* flags= */ 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a {@link VibrationEffect} to XML and writes output to given {@link Writer}.
|
||||
*
|
||||
* <p>Same as {@link #serialize(VibrationEffect, Writer)}, with extra flags to control the
|
||||
* serialization behavior.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer,
|
||||
@Flags int flags) throws SerializationFailedException, IOException {
|
||||
// Serialize effect first to fail early.
|
||||
XmlSerializedVibration<VibrationEffect> serializedVibration =
|
||||
toSerializedVibration(effect, flags);
|
||||
TypedXmlSerializer xmlSerializer = Xml.newFastSerializer();
|
||||
xmlSerializer.setFeature(XML_FEATURE_INDENT_OUTPUT, (flags & FLAG_PRETTY_PRINT) != 0);
|
||||
xmlSerializer.setOutput(writer);
|
||||
xmlSerializer.startDocument(XML_ENCODING, /* standalone= */ false);
|
||||
serializedVibration.write(xmlSerializer);
|
||||
xmlSerializer.endDocument();
|
||||
}
|
||||
|
||||
private static XmlSerializedVibration<VibrationEffect> toSerializedVibration(
|
||||
VibrationEffect effect, @Flags int flags) throws SerializationFailedException {
|
||||
XmlSerializedVibration<VibrationEffect> serializedVibration;
|
||||
int serializerFlags = 0;
|
||||
if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) {
|
||||
serializerFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
|
||||
}
|
||||
|
||||
try {
|
||||
serializableEffect = VibrationEffectXmlSerializer.serialize(effect);
|
||||
XmlValidator.checkSerializedVibration(serializableEffect, effect);
|
||||
serializedVibration = VibrationEffectXmlSerializer.serialize(effect, serializerFlags);
|
||||
XmlValidator.checkSerializedVibration(serializedVibration, effect);
|
||||
} catch (XmlSerializerException e) {
|
||||
// Serialization failed or created incomplete representation, fail before writing.
|
||||
throw new SerializationFailedException(effect, e);
|
||||
}
|
||||
|
||||
TypedXmlSerializer xmlSerializer = Xml.newFastSerializer();
|
||||
xmlSerializer.setFeature(SERIALIZER_FEATURE_INDENT_OUTPUT, false);
|
||||
xmlSerializer.setOutput(writer);
|
||||
xmlSerializer.startDocument(SERIALIZER_ENCODING, /* standalone= */ false);
|
||||
serializableEffect.write(xmlSerializer);
|
||||
xmlSerializer.endDocument();
|
||||
return serializedVibration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,9 +78,9 @@ final class SerializedCompositionPrimitive implements SerializedSegment {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SerializedCompositionPrimitive{"
|
||||
+ "primitiveName=" + mPrimitiveName
|
||||
+ ", primitiveScale=" + mPrimitiveScale
|
||||
+ ", primitiveDelayMs=" + mPrimitiveDelayMs
|
||||
+ "name=" + mPrimitiveName
|
||||
+ ", scale=" + mPrimitiveScale
|
||||
+ ", delayMs=" + mPrimitiveDelayMs
|
||||
+ '}';
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
package com.android.internal.vibrator.persistence;
|
||||
|
||||
import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_FALLBACK;
|
||||
import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_NAME;
|
||||
import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
|
||||
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.vibrator.PrebakedSegment;
|
||||
|
||||
import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
|
||||
import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName;
|
||||
@@ -33,34 +34,41 @@ import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Serialized representation of a predefined effect created via
|
||||
* {@link VibrationEffect#createPredefined(int)}.
|
||||
* {@link VibrationEffect#get(int, boolean)}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
final class SerializedPredefinedEffect implements SerializedSegment {
|
||||
|
||||
@NonNull
|
||||
private final PredefinedEffectName mEffectName;
|
||||
private final boolean mShouldFallback;
|
||||
|
||||
SerializedPredefinedEffect(PredefinedEffectName effectName) {
|
||||
SerializedPredefinedEffect(PredefinedEffectName effectName, boolean shouldFallback) {
|
||||
mEffectName = effectName;
|
||||
mShouldFallback = shouldFallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
|
||||
composition.addEffect(VibrationEffect.createPredefined(mEffectName.getEffectId()));
|
||||
composition.addEffect(VibrationEffect.get(mEffectName.getEffectId(), mShouldFallback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
|
||||
serializer.startTag(NAMESPACE, TAG_PREDEFINED_EFFECT);
|
||||
serializer.attribute(NAMESPACE, ATTRIBUTE_NAME, mEffectName.toString());
|
||||
if (mShouldFallback != PrebakedSegment.DEFAULT_SHOULD_FALLBACK) {
|
||||
serializer.attributeBoolean(NAMESPACE, ATTRIBUTE_FALLBACK, mShouldFallback);
|
||||
}
|
||||
serializer.endTag(NAMESPACE, TAG_PREDEFINED_EFFECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SerializedPredefinedEffect{"
|
||||
+ "effectName=" + mEffectName
|
||||
+ "name=" + mEffectName
|
||||
+ ", fallback=" + mShouldFallback
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@@ -68,31 +76,36 @@ final class SerializedPredefinedEffect implements SerializedSegment {
|
||||
static final class Parser {
|
||||
|
||||
@NonNull
|
||||
static SerializedPredefinedEffect parseNext(@NonNull TypedXmlPullParser parser)
|
||||
throws XmlParserException, IOException {
|
||||
static SerializedPredefinedEffect parseNext(@NonNull TypedXmlPullParser parser,
|
||||
@XmlConstants.Flags int flags) throws XmlParserException, IOException {
|
||||
XmlValidator.checkStartTag(parser, TAG_PREDEFINED_EFFECT);
|
||||
XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_NAME);
|
||||
|
||||
PredefinedEffectName effectName = parseEffectName(
|
||||
parser.getAttributeValue(NAMESPACE, ATTRIBUTE_NAME));
|
||||
boolean allowHidden = (flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) != 0;
|
||||
if (allowHidden) {
|
||||
XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_NAME,
|
||||
ATTRIBUTE_FALLBACK);
|
||||
} else {
|
||||
XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
String nameAttr = parser.getAttributeValue(NAMESPACE, ATTRIBUTE_NAME);
|
||||
if (nameAttr == null) {
|
||||
throw new XmlParserException("Missing predefined effect name");
|
||||
}
|
||||
PredefinedEffectName effectName = PredefinedEffectName.findByName(nameAttr, flags);
|
||||
if (effectName == null) {
|
||||
throw new XmlParserException("Unexpected predefined effect name " + nameAttr);
|
||||
}
|
||||
|
||||
boolean defaultFallback = PrebakedSegment.DEFAULT_SHOULD_FALLBACK;
|
||||
boolean fallback = allowHidden
|
||||
? parser.getAttributeBoolean(NAMESPACE, ATTRIBUTE_FALLBACK, defaultFallback)
|
||||
: defaultFallback;
|
||||
|
||||
// Consume tag
|
||||
XmlReader.readEndTag(parser);
|
||||
|
||||
return new SerializedPredefinedEffect(effectName);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static PredefinedEffectName parseEffectName(@Nullable String name)
|
||||
throws XmlParserException {
|
||||
if (name == null) {
|
||||
throw new XmlParserException("Missing predefined effect name");
|
||||
}
|
||||
PredefinedEffectName effectName = PredefinedEffectName.findByName(name);
|
||||
if (effectName == null) {
|
||||
throw new XmlParserException("Unexpected predefined effect name " + name);
|
||||
}
|
||||
return effectName;
|
||||
return new SerializedPredefinedEffect(effectName, fallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,16 +34,18 @@ import java.util.List;
|
||||
/**
|
||||
* Parser implementation for {@link VibrationEffect}.
|
||||
*
|
||||
* <p>This parser supports the schema defined by services/core/xsd/vibrator/vibration/vibration.xsd.
|
||||
*
|
||||
* <p>This parser does not support effects created with {@link VibrationEffect.WaveformBuilder} nor
|
||||
* {@link VibrationEffect.Composition#addEffect(VibrationEffect)}. It only supports vibration
|
||||
* effects defined as:
|
||||
*
|
||||
* * Predefined vibration effects
|
||||
*
|
||||
* <pre>VibrationEffect
|
||||
* <pre>
|
||||
* {@code
|
||||
* <vibration>
|
||||
* <predefined-effect id="0" />
|
||||
* <predefined-effect name="click" />
|
||||
* </vibration>
|
||||
* }
|
||||
* </pre>
|
||||
@@ -72,8 +74,8 @@ import java.util.List;
|
||||
* <pre>
|
||||
* {@code
|
||||
* <vibration>
|
||||
* <primitive-effect id="1" />
|
||||
* <primitive-effect id="2" scale="0.5" delayMs="100" />
|
||||
* <primitive-effect name="click" />
|
||||
* <primitive-effect name="tick" scale="0.5" delayMs="100" />
|
||||
* </vibration>
|
||||
* }
|
||||
* </pre>
|
||||
@@ -90,10 +92,12 @@ public class VibrationEffectXmlParser {
|
||||
*/
|
||||
@NonNull
|
||||
public static XmlSerializedVibration<VibrationEffect> parseTag(
|
||||
@NonNull TypedXmlPullParser parser) throws XmlParserException, IOException {
|
||||
@NonNull TypedXmlPullParser parser, @XmlConstants.Flags int flags)
|
||||
throws XmlParserException, IOException {
|
||||
XmlValidator.checkStartTag(parser, TAG_VIBRATION);
|
||||
XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
|
||||
return parseVibrationContent(parser);
|
||||
|
||||
return parseVibrationContent(parser, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,8 +107,8 @@ public class VibrationEffectXmlParser {
|
||||
* <p>This can be reused for reading a vibration from an XML root tag or from within a combined
|
||||
* vibration, but it should always be called from places that validates the top level tag.
|
||||
*/
|
||||
static SerializedVibrationEffect parseVibrationContent(TypedXmlPullParser parser)
|
||||
throws XmlParserException, IOException {
|
||||
static SerializedVibrationEffect parseVibrationContent(TypedXmlPullParser parser,
|
||||
@XmlConstants.Flags int flags) throws XmlParserException, IOException {
|
||||
String vibrationTagName = parser.getName();
|
||||
int vibrationTagDepth = parser.getDepth();
|
||||
|
||||
@@ -117,11 +121,15 @@ public class VibrationEffectXmlParser {
|
||||
switch (parser.getName()) {
|
||||
case TAG_PREDEFINED_EFFECT:
|
||||
serializedVibration = new SerializedVibrationEffect(
|
||||
SerializedPredefinedEffect.Parser.parseNext(parser));
|
||||
SerializedPredefinedEffect.Parser.parseNext(parser, flags));
|
||||
break;
|
||||
case TAG_PRIMITIVE_EFFECT:
|
||||
List<SerializedSegment> primitives = new ArrayList<>();
|
||||
do { // First primitive tag already open
|
||||
primitives.add(SerializedCompositionPrimitive.Parser.parseNext(parser));
|
||||
} while (XmlReader.readNextTagWithin(parser, vibrationTagDepth));
|
||||
serializedVibration = new SerializedVibrationEffect(
|
||||
parsePrimitiveList(parser, vibrationTagDepth));
|
||||
primitives.toArray(new SerializedSegment[primitives.size()]));
|
||||
break;
|
||||
case TAG_WAVEFORM_EFFECT:
|
||||
serializedVibration = new SerializedVibrationEffect(
|
||||
@@ -137,15 +145,4 @@ public class VibrationEffectXmlParser {
|
||||
|
||||
return serializedVibration;
|
||||
}
|
||||
|
||||
private static SerializedSegment[] parsePrimitiveList(
|
||||
TypedXmlPullParser parser, int outerDepth) throws XmlParserException, IOException {
|
||||
List<SerializedSegment> segments = new ArrayList<>();
|
||||
|
||||
do { // First primitive tag already open
|
||||
segments.add(SerializedCompositionPrimitive.Parser.parseNext(parser));
|
||||
} while (XmlReader.readNextTagWithin(parser, outerDepth));
|
||||
|
||||
return segments.toArray(new SerializedSegment[segments.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,41 +54,34 @@ public final class VibrationEffectXmlSerializer {
|
||||
*/
|
||||
@NonNull
|
||||
public static XmlSerializedVibration<VibrationEffect> serialize(
|
||||
@NonNull VibrationEffect vibration) throws XmlSerializerException {
|
||||
@NonNull VibrationEffect vibration, @XmlConstants.Flags int flags)
|
||||
throws XmlSerializerException {
|
||||
XmlValidator.checkSerializerCondition(vibration instanceof VibrationEffect.Composed,
|
||||
"Unsupported VibrationEffect type %s", vibration);
|
||||
|
||||
VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) vibration;
|
||||
XmlValidator.checkSerializerCondition(!composedEffect.getSegments().isEmpty(),
|
||||
VibrationEffect.Composed composed = (VibrationEffect.Composed) vibration;
|
||||
XmlValidator.checkSerializerCondition(!composed.getSegments().isEmpty(),
|
||||
"Unsupported empty VibrationEffect %s", vibration);
|
||||
|
||||
VibrationEffectSegment firstSegment = composedEffect.getSegments().get(0);
|
||||
VibrationEffectSegment firstSegment = composed.getSegments().get(0);
|
||||
if (firstSegment instanceof PrebakedSegment) {
|
||||
return serializePredefinedEffect(composedEffect);
|
||||
return serializePredefinedEffect(composed, flags);
|
||||
}
|
||||
if (firstSegment instanceof PrimitiveSegment) {
|
||||
return serializePrimitiveEffect(composedEffect);
|
||||
return serializePrimitiveEffect(composed);
|
||||
}
|
||||
return serializeWaveformEffect(composedEffect);
|
||||
return serializeWaveformEffect(composed);
|
||||
}
|
||||
|
||||
private static SerializedVibrationEffect serializePredefinedEffect(
|
||||
VibrationEffect.Composed effect) throws XmlSerializerException {
|
||||
VibrationEffect.Composed effect, @XmlConstants.Flags int flags)
|
||||
throws XmlSerializerException {
|
||||
List<VibrationEffectSegment> segments = effect.getSegments();
|
||||
XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
|
||||
"Unsupported repeating predefined effect %s", effect);
|
||||
XmlValidator.checkSerializerCondition(segments.size() == 1,
|
||||
"Unsupported multiple segments in predefined effect %s", effect);
|
||||
XmlValidator.checkSerializerCondition(segments.get(0) instanceof PrebakedSegment,
|
||||
"Unsupported segment for predefined effect %s", segments.get(0));
|
||||
|
||||
PrebakedSegment segment = (PrebakedSegment) segments.get(0);
|
||||
PredefinedEffectName effectName = PredefinedEffectName.findById(segment.getEffectId());
|
||||
|
||||
XmlValidator.checkSerializerCondition(effectName != null,
|
||||
"Unsupported predefined effect id %s", segment.getEffectId());
|
||||
|
||||
return new SerializedVibrationEffect(new SerializedPredefinedEffect(effectName));
|
||||
return new SerializedVibrationEffect(serializePrebakedSegment(segments.get(0), flags));
|
||||
}
|
||||
|
||||
private static SerializedVibrationEffect serializePrimitiveEffect(
|
||||
@@ -99,14 +92,7 @@ public final class VibrationEffectXmlSerializer {
|
||||
|
||||
SerializedSegment[] primitives = new SerializedSegment[segments.size()];
|
||||
for (int i = 0; i < segments.size(); i++) {
|
||||
XmlValidator.checkSerializerCondition(segments.get(i) instanceof PrimitiveSegment,
|
||||
"Unsupported segment for primitive composition %s", segments.get(i));
|
||||
|
||||
PrimitiveSegment segment = (PrimitiveSegment) segments.get(i);
|
||||
PrimitiveEffectName primitiveName =
|
||||
PrimitiveEffectName.findById(segment.getPrimitiveId());
|
||||
primitives[i] = new SerializedCompositionPrimitive(
|
||||
primitiveName, segment.getScale(), segment.getDelay());
|
||||
primitives[i] = serializePrimitiveSegment(segments.get(i));
|
||||
}
|
||||
|
||||
return new SerializedVibrationEffect(primitives);
|
||||
@@ -126,6 +112,10 @@ public final class VibrationEffectXmlSerializer {
|
||||
if (effect.getRepeatIndex() == i) {
|
||||
serializedWaveformBuilder.setRepeatIndexToCurrentEntry();
|
||||
}
|
||||
|
||||
XmlValidator.checkSerializerCondition(Float.compare(segment.getFrequencyHz(), 0) == 0,
|
||||
"Unsupported segment with non-default frequency %f", segment.getFrequencyHz());
|
||||
|
||||
serializedWaveformBuilder.addDurationAndAmplitude(
|
||||
segment.getDuration(), toAmplitudeInt(segment.getAmplitude()));
|
||||
}
|
||||
@@ -133,6 +123,46 @@ public final class VibrationEffectXmlSerializer {
|
||||
return new SerializedVibrationEffect(serializedWaveformBuilder.build());
|
||||
}
|
||||
|
||||
private static SerializedPredefinedEffect serializePrebakedSegment(
|
||||
VibrationEffectSegment segment, @XmlConstants.Flags int flags)
|
||||
throws XmlSerializerException {
|
||||
XmlValidator.checkSerializerCondition(segment instanceof PrebakedSegment,
|
||||
"Unsupported segment for predefined effect %s", segment);
|
||||
|
||||
PrebakedSegment prebaked = (PrebakedSegment) segment;
|
||||
PredefinedEffectName effectName = PredefinedEffectName.findById(
|
||||
prebaked.getEffectId(), flags);
|
||||
|
||||
XmlValidator.checkSerializerCondition(effectName != null,
|
||||
"Unsupported predefined effect id %s", prebaked.getEffectId());
|
||||
|
||||
if ((flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) == 0) {
|
||||
// Only allow effects with default fallback flag if using the public APIs schema.
|
||||
XmlValidator.checkSerializerCondition(
|
||||
prebaked.shouldFallback() == PrebakedSegment.DEFAULT_SHOULD_FALLBACK,
|
||||
"Unsupported predefined effect with should fallback %s",
|
||||
prebaked.shouldFallback());
|
||||
}
|
||||
|
||||
return new SerializedPredefinedEffect(effectName, prebaked.shouldFallback());
|
||||
}
|
||||
|
||||
private static SerializedCompositionPrimitive serializePrimitiveSegment(
|
||||
VibrationEffectSegment segment) throws XmlSerializerException {
|
||||
XmlValidator.checkSerializerCondition(segment instanceof PrimitiveSegment,
|
||||
"Unsupported segment for primitive composition %s", segment);
|
||||
|
||||
PrimitiveSegment primitive = (PrimitiveSegment) segment;
|
||||
PrimitiveEffectName primitiveName =
|
||||
PrimitiveEffectName.findById(primitive.getPrimitiveId());
|
||||
|
||||
XmlValidator.checkSerializerCondition(primitiveName != null,
|
||||
"Unsupported primitive effect id %s", primitive.getPrimitiveId());
|
||||
|
||||
return new SerializedCompositionPrimitive(
|
||||
primitiveName, primitive.getScale(), primitive.getDelay());
|
||||
}
|
||||
|
||||
private static int toAmplitudeInt(float amplitude) {
|
||||
return Float.compare(amplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0
|
||||
? VibrationEffect.DEFAULT_AMPLITUDE
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
|
||||
package com.android.internal.vibrator.persistence;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.VibrationEffect.Composition.PrimitiveType;
|
||||
import android.os.VibrationEffect.EffectType;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
@@ -42,6 +44,7 @@ public final class XmlConstants {
|
||||
public static final String TAG_REPEATING = "repeating";
|
||||
|
||||
public static final String ATTRIBUTE_NAME = "name";
|
||||
public static final String ATTRIBUTE_FALLBACK = "fallback";
|
||||
public static final String ATTRIBUTE_DURATION_MS = "durationMs";
|
||||
public static final String ATTRIBUTE_AMPLITUDE = "amplitude";
|
||||
public static final String ATTRIBUTE_SCALE = "scale";
|
||||
@@ -49,6 +52,20 @@ public final class XmlConstants {
|
||||
|
||||
public static final String VALUE_AMPLITUDE_DEFAULT = "default";
|
||||
|
||||
/**
|
||||
* Allow {@link VibrationEffect} hidden APIs to be used during parsing/serializing.
|
||||
*
|
||||
* <p>Use the schema at services/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd.
|
||||
*/
|
||||
public static final int FLAG_ALLOW_HIDDEN_APIS = 1 << 0;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "FLAG_" }, flag = true, value = {
|
||||
FLAG_ALLOW_HIDDEN_APIS
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Flags {}
|
||||
|
||||
/** Represent supported values for attribute name in {@link #TAG_PRIMITIVE_EFFECT} */
|
||||
public enum PrimitiveEffectName {
|
||||
LOW_TICK(VibrationEffect.Composition.PRIMITIVE_LOW_TICK),
|
||||
@@ -106,15 +123,38 @@ public final class XmlConstants {
|
||||
|
||||
/** Represent supported values for attribute name in {@link #TAG_PREDEFINED_EFFECT} */
|
||||
public enum PredefinedEffectName {
|
||||
TICK(VibrationEffect.EFFECT_TICK),
|
||||
CLICK(VibrationEffect.EFFECT_CLICK),
|
||||
HEAVY_CLICK(VibrationEffect.EFFECT_HEAVY_CLICK),
|
||||
DOUBLE_CLICK(VibrationEffect.EFFECT_DOUBLE_CLICK);
|
||||
// Public effects
|
||||
TICK(VibrationEffect.EFFECT_TICK, true),
|
||||
CLICK(VibrationEffect.EFFECT_CLICK, true),
|
||||
HEAVY_CLICK(VibrationEffect.EFFECT_HEAVY_CLICK, true),
|
||||
DOUBLE_CLICK(VibrationEffect.EFFECT_DOUBLE_CLICK, true),
|
||||
|
||||
@EffectType private final int mEffectId;
|
||||
// Hidden effects
|
||||
TEXTURE_TICK(VibrationEffect.EFFECT_TEXTURE_TICK, false),
|
||||
THUD(VibrationEffect.EFFECT_THUD, false),
|
||||
POP(VibrationEffect.EFFECT_POP, false),
|
||||
RINGTONE_1(VibrationEffect.RINGTONES[0], false),
|
||||
RINGTONE_2(VibrationEffect.RINGTONES[1], false),
|
||||
RINGTONE_3(VibrationEffect.RINGTONES[2], false),
|
||||
RINGTONE_4(VibrationEffect.RINGTONES[3], false),
|
||||
RINGTONE_5(VibrationEffect.RINGTONES[4], false),
|
||||
RINGTONE_6(VibrationEffect.RINGTONES[5], false),
|
||||
RINGTONE_7(VibrationEffect.RINGTONES[6], false),
|
||||
RINGTONE_8(VibrationEffect.RINGTONES[7], false),
|
||||
RINGTONE_9(VibrationEffect.RINGTONES[8], false),
|
||||
RINGTONE_10(VibrationEffect.RINGTONES[9], false),
|
||||
RINGTONE_11(VibrationEffect.RINGTONES[10], false),
|
||||
RINGTONE_12(VibrationEffect.RINGTONES[11], false),
|
||||
RINGTONE_13(VibrationEffect.RINGTONES[12], false),
|
||||
RINGTONE_14(VibrationEffect.RINGTONES[13], false),
|
||||
RINGTONE_15(VibrationEffect.RINGTONES[14], false);
|
||||
|
||||
PredefinedEffectName(@EffectType int id) {
|
||||
private final int mEffectId;
|
||||
private final boolean mIsPublic;
|
||||
|
||||
PredefinedEffectName(int id, boolean isPublic) {
|
||||
mEffectId = id;
|
||||
mIsPublic = isPublic;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,10 +162,11 @@ public final class XmlConstants {
|
||||
* none of the available names maps to the given id.
|
||||
*/
|
||||
@Nullable
|
||||
public static PredefinedEffectName findById(int effectId) {
|
||||
public static PredefinedEffectName findById(int effectId, @XmlConstants.Flags int flags) {
|
||||
boolean allowHidden = (flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) != 0;
|
||||
for (PredefinedEffectName name : PredefinedEffectName.values()) {
|
||||
if (name.mEffectId == effectId) {
|
||||
return name;
|
||||
return (name.mIsPublic || allowHidden) ? name : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -136,15 +177,18 @@ public final class XmlConstants {
|
||||
* none of the available names maps to the given name.
|
||||
*/
|
||||
@Nullable
|
||||
public static PredefinedEffectName findByName(@NonNull String effectName) {
|
||||
public static PredefinedEffectName findByName(@NonNull String effectName,
|
||||
@XmlConstants.Flags int flags) {
|
||||
boolean allowHidden = (flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) != 0;
|
||||
try {
|
||||
return PredefinedEffectName.valueOf(effectName.toUpperCase(Locale.ROOT));
|
||||
PredefinedEffectName name = PredefinedEffectName.valueOf(
|
||||
effectName.toUpperCase(Locale.ROOT));
|
||||
return (name.mIsPublic || allowHidden) ? name : null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@EffectType
|
||||
public int getEffectId() {
|
||||
return mEffectId;
|
||||
}
|
||||
|
||||
@@ -149,25 +149,19 @@ public final class XmlReader {
|
||||
* attribute is missing.
|
||||
*/
|
||||
public static int readAttributeIntNonNegative(
|
||||
TypedXmlPullParser parser, String attributeName, int defaultValue)
|
||||
TypedXmlPullParser parser, String attrName, int defaultValue)
|
||||
throws XmlParserException {
|
||||
if (parser.getAttributeIndex(NAMESPACE, attributeName) < 0) {
|
||||
if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
return readAttributeIntNonNegative(parser, attributeName);
|
||||
return readAttributeIntNonNegative(parser, attrName);
|
||||
}
|
||||
|
||||
/** Read attribute from current tag as a non-negative integer. */
|
||||
public static int readAttributeIntNonNegative(TypedXmlPullParser parser, String attrName)
|
||||
throws XmlParserException {
|
||||
String tagName = parser.getName();
|
||||
int value;
|
||||
try {
|
||||
value = parser.getAttributeInt(NAMESPACE, attrName);
|
||||
} catch (XmlPullParserException e) {
|
||||
String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
|
||||
throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
|
||||
}
|
||||
int value = readAttributeInt(parser, attrName);
|
||||
|
||||
XmlValidator.checkParserCondition(value >= 0,
|
||||
"Unexpected %s = %d in tag %s, expected %s >= 0",
|
||||
@@ -180,13 +174,7 @@ public final class XmlReader {
|
||||
TypedXmlPullParser parser, String attrName, int lowerInclusive, int upperInclusive)
|
||||
throws XmlParserException {
|
||||
String tagName = parser.getName();
|
||||
int value;
|
||||
try {
|
||||
value = parser.getAttributeInt(NAMESPACE, attrName);
|
||||
} catch (XmlPullParserException e) {
|
||||
String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
|
||||
throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
|
||||
}
|
||||
int value = readAttributeInt(parser, attrName);
|
||||
|
||||
XmlValidator.checkParserCondition(
|
||||
value >= lowerInclusive && value <= upperInclusive,
|
||||
@@ -206,18 +194,33 @@ public final class XmlReader {
|
||||
return defaultValue;
|
||||
}
|
||||
String tagName = parser.getName();
|
||||
float value;
|
||||
try {
|
||||
// The method getAttributeFloat with default catches any Exception and returns default.
|
||||
value = parser.getAttributeFloat(NAMESPACE, attrName);
|
||||
} catch (XmlPullParserException e) {
|
||||
String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
|
||||
throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
|
||||
}
|
||||
float value = readAttributeFloat(parser, attrName);
|
||||
|
||||
XmlValidator.checkParserCondition(value >= lowerInclusive && value <= upperInclusive,
|
||||
"Unexpected %s = %f in tag %s, expected %s in [%f, %f]",
|
||||
attrName, value, tagName, attrName, lowerInclusive, upperInclusive);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static int readAttributeInt(TypedXmlPullParser parser, String attrName)
|
||||
throws XmlParserException {
|
||||
String tagName = parser.getName();
|
||||
try {
|
||||
return parser.getAttributeInt(NAMESPACE, attrName);
|
||||
} catch (XmlPullParserException e) {
|
||||
String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
|
||||
throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static float readAttributeFloat(TypedXmlPullParser parser, String attrName)
|
||||
throws XmlParserException {
|
||||
String tagName = parser.getName();
|
||||
try {
|
||||
return parser.getAttributeFloat(NAMESPACE, attrName);
|
||||
} catch (XmlPullParserException e) {
|
||||
String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
|
||||
throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 android.os.vibrator.persistence;
|
||||
|
||||
import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
|
||||
import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
|
||||
import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
|
||||
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.vibrator.PrebakedSegment;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link VibrationXmlParser} and {@link VibrationXmlSerializer}.
|
||||
*
|
||||
* <p>The {@link VibrationEffect} public APIs are covered by CTS to enforce the schema defined at
|
||||
* services/core/xsd/vibrator/vibration/vibration.xsd.
|
||||
*/
|
||||
@Presubmit
|
||||
@RunWith(JUnit4.class)
|
||||
public class VibrationEffectXmlSerializationTest {
|
||||
|
||||
@Test
|
||||
public void testPrimitives_allSucceed() throws IOException {
|
||||
VibrationEffect effect = VibrationEffect.startComposition()
|
||||
.addPrimitive(PRIMITIVE_CLICK)
|
||||
.addPrimitive(PRIMITIVE_TICK, 0.2497f)
|
||||
.addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
|
||||
.addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
|
||||
.compose();
|
||||
String xml = "<vibration>"
|
||||
+ "<primitive-effect name=\"click\"/>"
|
||||
+ "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
|
||||
+ "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
|
||||
+ "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
|
||||
+ "</vibration>";
|
||||
|
||||
assertPublicApisParserSucceeds(xml, effect);
|
||||
assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
|
||||
assertPublicApisRoundTrip(effect);
|
||||
|
||||
assertHiddenApisParserSucceeds(xml, effect);
|
||||
assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
|
||||
assertHiddenApisRoundTrip(effect);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWaveforms_allSucceed() throws IOException {
|
||||
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0},
|
||||
new int[]{254, 1, 255, 0}, /* repeat= */ 0);
|
||||
String xml = "<vibration>"
|
||||
+ "<waveform-effect><repeating>"
|
||||
+ "<waveform-entry durationMs=\"123\" amplitude=\"254\"/>"
|
||||
+ "<waveform-entry durationMs=\"456\" amplitude=\"1\"/>"
|
||||
+ "<waveform-entry durationMs=\"789\" amplitude=\"255\"/>"
|
||||
+ "<waveform-entry durationMs=\"0\" amplitude=\"0\"/>"
|
||||
+ "</repeating></waveform-effect>"
|
||||
+ "</vibration>";
|
||||
|
||||
assertPublicApisParserSucceeds(xml, effect);
|
||||
assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
|
||||
assertPublicApisRoundTrip(effect);
|
||||
|
||||
assertHiddenApisParserSucceeds(xml, effect);
|
||||
assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
|
||||
assertHiddenApisRoundTrip(effect);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPredefinedEffects_publicEffectsWithDefaultFallback_allSucceed()
|
||||
throws IOException {
|
||||
for (Map.Entry<String, Integer> entry : createPublicPredefinedEffectsMap().entrySet()) {
|
||||
VibrationEffect effect = VibrationEffect.get(entry.getValue());
|
||||
String xml = String.format(
|
||||
"<vibration><predefined-effect name=\"%s\"/></vibration>", entry.getKey());
|
||||
|
||||
assertPublicApisParserSucceeds(xml, effect);
|
||||
assertPublicApisSerializerSucceeds(effect, entry.getKey());
|
||||
assertPublicApisRoundTrip(effect);
|
||||
|
||||
assertHiddenApisParserSucceeds(xml, effect);
|
||||
assertHiddenApisSerializerSucceeds(effect, entry.getKey());
|
||||
assertHiddenApisRoundTrip(effect);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPredefinedEffects_hiddenEffects_onlySucceedsWithFlag() throws IOException {
|
||||
for (Map.Entry<String, Integer> entry : createHiddenPredefinedEffectsMap().entrySet()) {
|
||||
VibrationEffect effect = VibrationEffect.get(entry.getValue());
|
||||
String xml = String.format(
|
||||
"<vibration><predefined-effect name=\"%s\"/></vibration>", entry.getKey());
|
||||
|
||||
assertPublicApisParserFails(xml);
|
||||
assertPublicApisSerializerFails(effect);
|
||||
|
||||
assertHiddenApisParserSucceeds(xml, effect);
|
||||
assertHiddenApisSerializerSucceeds(effect, entry.getKey());
|
||||
assertHiddenApisRoundTrip(effect);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPredefinedEffects_allEffectsWithNonDefaultFallback_onlySucceedsWithFlag()
|
||||
throws IOException {
|
||||
for (Map.Entry<String, Integer> entry : createAllPredefinedEffectsMap().entrySet()) {
|
||||
boolean nonDefaultFallback = !PrebakedSegment.DEFAULT_SHOULD_FALLBACK;
|
||||
VibrationEffect effect = VibrationEffect.get(entry.getValue(), nonDefaultFallback);
|
||||
String xml = String.format(
|
||||
"<vibration><predefined-effect name=\"%s\" fallback=\"%s\"/></vibration>",
|
||||
entry.getKey(), nonDefaultFallback);
|
||||
|
||||
assertPublicApisParserFails(xml);
|
||||
assertPublicApisSerializerFails(effect);
|
||||
|
||||
assertHiddenApisParserSucceeds(xml, effect);
|
||||
assertHiddenApisSerializerSucceeds(effect, entry.getKey());
|
||||
assertHiddenApisRoundTrip(effect);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertPublicApisParserFails(String xml) throws IOException {
|
||||
assertThat(parse(xml, /* flags= */ 0)).isNull();
|
||||
}
|
||||
|
||||
private void assertPublicApisParserSucceeds(String xml, VibrationEffect effect)
|
||||
throws IOException {
|
||||
assertThat(parse(xml, /* flags= */ 0)).isEqualTo(effect);
|
||||
}
|
||||
|
||||
private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect)
|
||||
throws IOException {
|
||||
assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect);
|
||||
}
|
||||
|
||||
private void assertPublicApisSerializerFails(VibrationEffect effect) {
|
||||
assertThrows("Expected serialization to fail for " + effect,
|
||||
VibrationXmlSerializer.SerializationFailedException.class,
|
||||
() -> serialize(effect, /* flags= */ 0));
|
||||
}
|
||||
|
||||
private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
|
||||
String... expectedSegments) throws IOException {
|
||||
assertSerializationContainsSegments(serialize(effect, /* flags= */ 0), expectedSegments);
|
||||
}
|
||||
|
||||
private void assertHiddenApisSerializerSucceeds(VibrationEffect effect,
|
||||
String... expectedSegments) throws IOException {
|
||||
assertSerializationContainsSegments(
|
||||
serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS), expectedSegments);
|
||||
}
|
||||
|
||||
private void assertSerializationContainsSegments(String xml, String[] expectedSegments) {
|
||||
for (String expectedSegment : expectedSegments) {
|
||||
assertThat(xml).contains(expectedSegment);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertPublicApisRoundTrip(VibrationEffect effect) throws IOException {
|
||||
assertThat(parse(serialize(effect, /* flags= */ 0), /* flags= */ 0)).isEqualTo(effect);
|
||||
}
|
||||
|
||||
private void assertHiddenApisRoundTrip(VibrationEffect effect) throws IOException {
|
||||
String xml = serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS);
|
||||
assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect);
|
||||
}
|
||||
|
||||
private static VibrationEffect parse(String xml, @VibrationXmlParser.Flags int flags)
|
||||
throws IOException {
|
||||
return VibrationXmlParser.parse(new StringReader(xml), flags);
|
||||
}
|
||||
|
||||
private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags)
|
||||
throws IOException {
|
||||
StringWriter writer = new StringWriter();
|
||||
VibrationXmlSerializer.serialize(effect, writer, flags);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private static Map<String, Integer> createAllPredefinedEffectsMap() {
|
||||
Map<String, Integer> map = createHiddenPredefinedEffectsMap();
|
||||
map.putAll(createPublicPredefinedEffectsMap());
|
||||
return map;
|
||||
}
|
||||
|
||||
private static Map<String, Integer> createPublicPredefinedEffectsMap() {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
map.put("tick", VibrationEffect.EFFECT_TICK);
|
||||
map.put("click", VibrationEffect.EFFECT_CLICK);
|
||||
map.put("heavy_click", VibrationEffect.EFFECT_HEAVY_CLICK);
|
||||
map.put("double_click", VibrationEffect.EFFECT_DOUBLE_CLICK);
|
||||
return map;
|
||||
}
|
||||
|
||||
private static Map<String, Integer> createHiddenPredefinedEffectsMap() {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
map.put("texture_tick", VibrationEffect.EFFECT_TEXTURE_TICK);
|
||||
map.put("pop", VibrationEffect.EFFECT_POP);
|
||||
map.put("thud", VibrationEffect.EFFECT_THUD);
|
||||
for (int i = 0; i < VibrationEffect.RINGTONES.length; i++) {
|
||||
map.put(String.format("ringtone_%d", i + 1), VibrationEffect.RINGTONES[i]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
157
core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
Normal file
157
core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
Normal file
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2023 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.
|
||||
-->
|
||||
<!--
|
||||
This defines the format of the XML file used to define VibrationEffect created via public and
|
||||
hidden APIs. This format is not enforced in a backwards compatible way, and should only be used
|
||||
on device-specific vibration files.
|
||||
-->
|
||||
<xs:schema version="2.0"
|
||||
elementFormDefault="qualified"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:element name="vibration" type="Vibration"/>
|
||||
|
||||
<!-- Type definitions -->
|
||||
|
||||
<xs:complexType name="Vibration">
|
||||
<xs:choice>
|
||||
|
||||
<!-- Waveform vibration effect -->
|
||||
<xs:element name="waveform-effect" type="WaveformEffect"/>
|
||||
|
||||
<!-- Predefined vibration effect -->
|
||||
<xs:element name="predefined-effect" type="PredefinedEffect"/>
|
||||
|
||||
<!-- Primitive composition effect -->
|
||||
<xs:sequence>
|
||||
<xs:element name="primitive-effect" type="PrimitiveEffect"/>
|
||||
</xs:sequence>
|
||||
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="WaveformEffect">
|
||||
<xs:sequence>
|
||||
|
||||
<!-- Optional preamble, zero or more entries -->
|
||||
<xs:element name="waveform-entry" type="WaveformEntry"
|
||||
minOccurs="0" maxOccurs="unbounded"/>
|
||||
|
||||
<!-- Repeating element, with one or more entries -->
|
||||
<xs:element name="repeating" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="waveform-entry" type="WaveformEntry"
|
||||
maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
</xs:sequence>
|
||||
<!-- Unsupported by xsd_config, but will be used in validation:
|
||||
<xs:assert test="fn:count(./waveform-entry) > 0 or fn:count(./repeating) > 0)"/>
|
||||
-->
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="WaveformEntry">
|
||||
<xs:attribute name="durationMs" type="xs:nonNegativeInteger" use="required"/>
|
||||
<xs:attribute name="amplitude" type="WaveformAmplitude" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="WaveformAmplitude">
|
||||
<xs:union memberTypes="WaveformAmplitudeInt WaveformAmplitudeDefault"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- Amplitude int in [0,255] -->
|
||||
<xs:simpleType name="WaveformAmplitudeInt">
|
||||
<xs:restriction base="xs:int">
|
||||
<xs:minInclusive value="0"/>
|
||||
<xs:maxInclusive value="255"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- Default amplitude as defined by VibrationEffect.DEFAULT_AMPLITUDE -->
|
||||
<xs:simpleType name="WaveformAmplitudeDefault">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="default"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="PredefinedEffect">
|
||||
<xs:attribute name="name" type="PredefinedEffectName" use="required"/>
|
||||
<xs:attribute name="fallback" type="xs:boolean"/><!-- hidden -->
|
||||
</xs:complexType>
|
||||
|
||||
<!-- Predefined effect names as defined by VibrationEffect.EFFECT_* -->
|
||||
<xs:simpleType name="PredefinedEffectName">
|
||||
<xs:restriction base="xs:string">
|
||||
<!-- Public effects -->
|
||||
<xs:enumeration value="tick"/>
|
||||
<xs:enumeration value="click"/>
|
||||
<xs:enumeration value="heavy_click"/>
|
||||
<xs:enumeration value="double_click"/>
|
||||
<!-- Hidden effects -->
|
||||
<xs:enumeration value="texture_tick"/>
|
||||
<xs:enumeration value="thud"/>
|
||||
<xs:enumeration value="pop"/>
|
||||
<xs:enumeration value="ringtone_1"/>
|
||||
<xs:enumeration value="ringtone_2"/>
|
||||
<xs:enumeration value="ringtone_3"/>
|
||||
<xs:enumeration value="ringtone_4"/>
|
||||
<xs:enumeration value="ringtone_5"/>
|
||||
<xs:enumeration value="ringtone_6"/>
|
||||
<xs:enumeration value="ringtone_7"/>
|
||||
<xs:enumeration value="ringtone_8"/>
|
||||
<xs:enumeration value="ringtone_9"/>
|
||||
<xs:enumeration value="ringtone_10"/>
|
||||
<xs:enumeration value="ringtone_11"/>
|
||||
<xs:enumeration value="ringtone_12"/>
|
||||
<xs:enumeration value="ringtone_13"/>
|
||||
<xs:enumeration value="ringtone_14"/>
|
||||
<xs:enumeration value="ringtone_15"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="PrimitiveEffect">
|
||||
<xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
|
||||
<xs:attribute name="scale" type="PrimitiveScale"/>
|
||||
<xs:attribute name="delayMs" type="xs:nonNegativeInteger"/>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- Primitive names as defined by VibrationEffect.Composition.PRIMITIVE_* -->
|
||||
<xs:simpleType name="PrimitiveEffectName">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="low_tick"/>
|
||||
<xs:enumeration value="tick"/>
|
||||
<xs:enumeration value="click"/>
|
||||
<xs:enumeration value="slow_rise"/>
|
||||
<xs:enumeration value="quick_rise"/>
|
||||
<xs:enumeration value="quick_fall"/>
|
||||
<xs:enumeration value="spin"/>
|
||||
<xs:enumeration value="thud"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- Primitive scale float in [0,1] -->
|
||||
<xs:simpleType name="PrimitiveScale">
|
||||
<xs:restriction base="xs:float">
|
||||
<xs:minInclusive value="0"/>
|
||||
<xs:maxInclusive value="1"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
@@ -90,7 +90,7 @@
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="PredefinedEffect">
|
||||
<xs:attribute name="name" type="PredefinedEffectName"/>
|
||||
<xs:attribute name="name" type="PredefinedEffectName" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- Predefined effect names as defined by VibrationEffect.EFFECT_* -->
|
||||
@@ -104,7 +104,7 @@
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="PrimitiveEffect">
|
||||
<xs:attribute name="name" type="PrimitiveEffectName"/>
|
||||
<xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
|
||||
<xs:attribute name="scale" type="PrimitiveScale"/>
|
||||
<xs:attribute name="delayMs" type="xs:nonNegativeInteger"/>
|
||||
</xs:complexType>
|
||||
|
||||
Reference in New Issue
Block a user