diff options
| author | 2024-11-26 13:44:41 +0000 | |
|---|---|---|
| committer | 2024-11-29 22:23:31 +0000 | |
| commit | 7dba6f419b2b6f063e8a2616da62caad8fef84da (patch) | |
| tree | 8c441950fa60de31139b58c6e60ae0c1fd03c402 | |
| parent | f26b150c127d21081e422f6854079e6db0a78f0f (diff) | |
Add xml serialization of envelope vibration effects
Introduce new tags to the vibration.xsd scheme to support waveform and basic envelope effects.
Bug: 347035918
Flag: android.os.vibrator.normalized_pwle_effects
Test: atest android.os.vibrator.persistence
Change-Id: I7a7a7a492df13844a91c6320dfbe2d62cc01109e
10 files changed, 1177 insertions, 2 deletions
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java new file mode 100644 index 000000000000..a090c7abc2db --- /dev/null +++ b/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2024 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 com.android.internal.vibrator.persistence; + +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS; +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INITIAL_SHARPNESS; +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INTENSITY; +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_SHARPNESS; +import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_CONTROL_POINT; + +import android.annotation.NonNull; +import android.os.VibrationEffect; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * Serialized representation of a basic envelope effect created via + * {@link VibrationEffect.BasicEnvelopeBuilder}. + * + * @hide + */ +final class SerializedBasicEnvelopeEffect implements SerializedComposedEffect.SerializedSegment { + private final BasicControlPoint[] mControlPoints; + private final float mInitialSharpness; + + SerializedBasicEnvelopeEffect(BasicControlPoint[] controlPoints, float initialSharpness) { + mControlPoints = controlPoints; + mInitialSharpness = initialSharpness; + } + + @Override + public void write(@NonNull TypedXmlSerializer serializer) throws IOException { + serializer.startTag(NAMESPACE, TAG_BASIC_ENVELOPE_EFFECT); + + if (!Float.isNaN(mInitialSharpness)) { + serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INITIAL_SHARPNESS, mInitialSharpness); + } + + for (BasicControlPoint point : mControlPoints) { + serializer.startTag(NAMESPACE, TAG_CONTROL_POINT); + serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INTENSITY, point.mIntensity); + serializer.attributeFloat(NAMESPACE, ATTRIBUTE_SHARPNESS, point.mSharpness); + serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, point.mDurationMs); + serializer.endTag(NAMESPACE, TAG_CONTROL_POINT); + } + + serializer.endTag(NAMESPACE, TAG_BASIC_ENVELOPE_EFFECT); + } + + @Override + public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) { + VibrationEffect.BasicEnvelopeBuilder builder = new VibrationEffect.BasicEnvelopeBuilder(); + + if (!Float.isNaN(mInitialSharpness)) { + builder.setInitialSharpness(mInitialSharpness); + } + + for (BasicControlPoint point : mControlPoints) { + builder.addControlPoint(point.mIntensity, point.mSharpness, point.mDurationMs); + } + composition.addEffect(builder.build()); + } + + @Override + public String toString() { + return "SerializedBasicEnvelopeEffect{" + + "initialSharpness=" + (Float.isNaN(mInitialSharpness) ? "" : mInitialSharpness) + + ", controlPoints=" + Arrays.toString(mControlPoints) + + '}'; + } + + static final class Builder { + private final List<BasicControlPoint> mControlPoints; + private float mInitialSharpness = Float.NaN; + + Builder() { + mControlPoints = new ArrayList<>(); + } + + void setInitialSharpness(float sharpness) { + mInitialSharpness = sharpness; + } + + void addControlPoint(float intensity, float sharpness, long durationMs) { + mControlPoints.add(new BasicControlPoint(intensity, sharpness, durationMs)); + } + + SerializedBasicEnvelopeEffect build() { + return new SerializedBasicEnvelopeEffect( + mControlPoints.toArray(new BasicControlPoint[0]), mInitialSharpness); + } + } + + /** Parser implementation for {@link SerializedBasicEnvelopeEffect}. */ + static final class Parser { + + @NonNull + static SerializedBasicEnvelopeEffect parseNext(@NonNull TypedXmlPullParser parser, + @XmlConstants.Flags int flags) throws XmlParserException, IOException { + XmlValidator.checkStartTag(parser, TAG_BASIC_ENVELOPE_EFFECT); + XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_INITIAL_SHARPNESS); + + Builder builder = new Builder(); + builder.setInitialSharpness( + XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_INITIAL_SHARPNESS, 0f, 1f, + Float.NaN)); + + int outerDepth = parser.getDepth(); + + // Read all nested tags + while (XmlReader.readNextTagWithin(parser, outerDepth)) { + parseControlPoint(parser, builder); + // Consume tag + XmlReader.readEndTag(parser); + } + + // Check schema assertions about <basic-envelope-effect> + XmlValidator.checkParserCondition(!builder.mControlPoints.isEmpty(), + "Expected tag %s to have at least one control point", + TAG_BASIC_ENVELOPE_EFFECT); + XmlValidator.checkParserCondition(builder.mControlPoints.getLast().mIntensity == 0, + "Basic envelope effects must end at a zero intensity control point"); + + return builder.build(); + } + + private static void parseControlPoint(TypedXmlPullParser parser, Builder builder) + throws XmlParserException { + XmlValidator.checkStartTag(parser, TAG_CONTROL_POINT); + XmlValidator.checkTagHasNoUnexpectedAttributes( + parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_INTENSITY, + ATTRIBUTE_SHARPNESS); + float intensity = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_INTENSITY, 0, + 1); + float sharpness = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_SHARPNESS, 0, + 1); + long durationMs = XmlReader.readAttributePositiveLong(parser, ATTRIBUTE_DURATION_MS); + + builder.addControlPoint(intensity, sharpness, durationMs); + } + } + + private static final class BasicControlPoint { + private final float mIntensity; + private final float mSharpness; + private final long mDurationMs; + + BasicControlPoint(float intensity, float sharpness, long durationMs) { + mIntensity = intensity; + mSharpness = sharpness; + mDurationMs = durationMs; + } + + @Override + public String toString() { + return String.format(Locale.ROOT, "(%.2f, %.2f, %dms)", mIntensity, mSharpness, + mDurationMs); + } + } +} + diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java new file mode 100644 index 000000000000..6a893430d7ad --- /dev/null +++ b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2024 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 com.android.internal.vibrator.persistence; + +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_AMPLITUDE; +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS; +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_FREQUENCY_HZ; +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INITIAL_FREQUENCY_HZ; +import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_CONTROL_POINT; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT; + +import android.annotation.NonNull; +import android.os.VibrationEffect; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * Serialized representation of a waveform envelope effect created via + * {@link VibrationEffect.WaveformEnvelopeBuilder}. + * + * @hide + */ +final class SerializedWaveformEnvelopeEffect implements SerializedComposedEffect.SerializedSegment { + + private final WaveformControlPoint[] mControlPoints; + private final float mInitialFrequency; + + SerializedWaveformEnvelopeEffect(WaveformControlPoint[] controlPoints, float initialFrequency) { + mControlPoints = controlPoints; + mInitialFrequency = initialFrequency; + } + + @Override + public void write(@NonNull TypedXmlSerializer serializer) throws IOException { + serializer.startTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT); + + if (!Float.isNaN(mInitialFrequency)) { + serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INITIAL_FREQUENCY_HZ, mInitialFrequency); + } + + for (WaveformControlPoint point : mControlPoints) { + serializer.startTag(NAMESPACE, TAG_CONTROL_POINT); + serializer.attributeFloat(NAMESPACE, ATTRIBUTE_AMPLITUDE, point.mAmplitude); + serializer.attributeFloat(NAMESPACE, ATTRIBUTE_FREQUENCY_HZ, point.mFrequency); + serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, point.mDurationMs); + serializer.endTag(NAMESPACE, TAG_CONTROL_POINT); + } + + serializer.endTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT); + } + + @Override + public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) { + VibrationEffect.WaveformEnvelopeBuilder builder = + new VibrationEffect.WaveformEnvelopeBuilder(); + + if (!Float.isNaN(mInitialFrequency)) { + builder.setInitialFrequencyHz(mInitialFrequency); + } + + for (WaveformControlPoint point : mControlPoints) { + builder.addControlPoint(point.mAmplitude, point.mFrequency, point.mDurationMs); + } + composition.addEffect(builder.build()); + } + + @Override + public String toString() { + return "SerializedWaveformEnvelopeEffect{" + + "InitialFrequency=" + (Float.isNaN(mInitialFrequency) ? "" : mInitialFrequency) + + ", controlPoints=" + Arrays.toString(mControlPoints) + + '}'; + } + + static final class Builder { + private final List<WaveformControlPoint> mControlPoints; + private float mInitialFrequencyHz = Float.NaN; + + Builder() { + mControlPoints = new ArrayList<>(); + } + + void setInitialFrequencyHz(float frequencyHz) { + mInitialFrequencyHz = frequencyHz; + } + + void addControlPoint(float amplitude, float frequencyHz, long durationMs) { + mControlPoints.add(new WaveformControlPoint(amplitude, frequencyHz, durationMs)); + } + + SerializedWaveformEnvelopeEffect build() { + return new SerializedWaveformEnvelopeEffect( + mControlPoints.toArray(new WaveformControlPoint[0]), mInitialFrequencyHz); + } + } + + /** Parser implementation for {@link SerializedWaveformEnvelopeEffect}. */ + static final class Parser { + + @NonNull + static SerializedWaveformEnvelopeEffect parseNext(@NonNull TypedXmlPullParser parser, + @XmlConstants.Flags int flags) throws XmlParserException, IOException { + XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENVELOPE_EFFECT); + XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ); + + Builder builder = new Builder(); + builder.setInitialFrequencyHz( + XmlReader.readAttributePositiveFloat(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ, + Float.NaN)); + + int outerDepth = parser.getDepth(); + + while (XmlReader.readNextTagWithin(parser, outerDepth)) { + parseControlPoint(parser, builder); + // Consume tag + XmlReader.readEndTag(parser); + } + + // Check schema assertions about <waveform-envelope-effect> + XmlValidator.checkParserCondition(!builder.mControlPoints.isEmpty(), + "Expected tag %s to have at least one control point", + TAG_WAVEFORM_ENVELOPE_EFFECT); + + return builder.build(); + } + + private static void parseControlPoint(TypedXmlPullParser parser, Builder builder) + throws XmlParserException { + XmlValidator.checkStartTag(parser, TAG_CONTROL_POINT); + XmlValidator.checkTagHasNoUnexpectedAttributes( + parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE, + ATTRIBUTE_FREQUENCY_HZ); + float amplitude = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_AMPLITUDE, 0, + 1); + float frequencyHz = XmlReader.readAttributePositiveFloat(parser, + ATTRIBUTE_FREQUENCY_HZ); + long durationMs = XmlReader.readAttributePositiveLong(parser, ATTRIBUTE_DURATION_MS); + + builder.addControlPoint(amplitude, frequencyHz, durationMs); + } + } + + private static final class WaveformControlPoint { + private final float mAmplitude; + private final float mFrequency; + private final long mDurationMs; + + WaveformControlPoint(float amplitude, float frequency, long durationMs) { + mAmplitude = amplitude; + mFrequency = frequency; + mDurationMs = durationMs; + } + + @Override + public String toString() { + return String.format(Locale.ROOT, "(%.2f, %.2f, %dms)", mAmplitude, mFrequency, + mDurationMs); + } + } +} diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java index a9fbcafa128d..314bfe40ee0b 100644 --- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java +++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java @@ -16,11 +16,13 @@ package com.android.internal.vibrator.persistence; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT; import android.annotation.NonNull; import android.os.VibrationEffect; @@ -92,6 +94,32 @@ import java.util.List; * } * </pre> * + * * Waveform Envelope effects + * + * <pre> + * {@code + * <vibration-effect> + * <waveform-envelope-effect initialFrequencyHz="20.0"> + * <control-point amplitude="0.2" frequencyHz="80.0" durationMs="50" /> + * <control-point amplitude="0.5" frequencyHz="150.0" durationMs="50" /> + * </envelope-effect> + * </vibration-effect> + * } + * </pre> + * + * * Basic Envelope effects + * + * <pre> + * {@code + * <vibration-effect> + * <basic-envelope-effect initialSharpness="0.3"> + * <control-point intensity="0.2" sharpness="0.5" durationMs="50" /> + * <control-point intensity="0.0" sharpness="1.0" durationMs="50" /> + * </envelope-effect> + * </vibration-effect> + * } + * </pre> + * * @hide */ public class VibrationEffectXmlParser { @@ -151,6 +179,18 @@ public class VibrationEffectXmlParser { serializedVibration = new SerializedComposedEffect( SerializedAmplitudeStepWaveform.Parser.parseNext(parser)); break; + case TAG_WAVEFORM_ENVELOPE_EFFECT: + if (Flags.normalizedPwleEffects()) { + serializedVibration = new SerializedComposedEffect( + SerializedWaveformEnvelopeEffect.Parser.parseNext(parser, flags)); + break; + } // else fall through + case TAG_BASIC_ENVELOPE_EFFECT: + if (Flags.normalizedPwleEffects()) { + serializedVibration = new SerializedComposedEffect( + SerializedBasicEnvelopeEffect.Parser.parseNext(parser, flags)); + break; + } // else fall through default: throw new XmlParserException("Unexpected tag " + parser.getName() + " in vibration tag " + vibrationTagName); diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java index cb834a5eac7e..ebe34344c6f5 100644 --- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java +++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java @@ -19,9 +19,11 @@ package com.android.internal.vibrator.persistence; import android.annotation.NonNull; import android.os.PersistableBundle; import android.os.VibrationEffect; +import android.os.vibrator.BasicPwleSegment; import android.os.vibrator.Flags; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.PwleSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationEffectSegment; @@ -45,6 +47,8 @@ import java.util.List; * <li>A composition created exclusively via * {@link VibrationEffect.Composition#addPrimitive(int, float, int)} * <li>{@link VibrationEffect#createVendorEffect(PersistableBundle)} + * <li>{@link VibrationEffect.WaveformEnvelopeBuilder} + * <li>{@link VibrationEffect.BasicEnvelopeBuilder} * </ul> * * @hide @@ -77,6 +81,12 @@ public final class VibrationEffectXmlSerializer { if (firstSegment instanceof PrimitiveSegment) { return serializePrimitiveEffect(composed); } + if (Flags.normalizedPwleEffects() && firstSegment instanceof PwleSegment) { + return serializeWaveformEnvelopeEffect(composed); + } + if (Flags.normalizedPwleEffects() && firstSegment instanceof BasicPwleSegment) { + return serializeBasicEnvelopeEffect(composed); + } return serializeWaveformEffect(composed); } @@ -110,6 +120,53 @@ public final class VibrationEffectXmlSerializer { return new SerializedComposedEffect(primitives); } + private static SerializedComposedEffect serializeWaveformEnvelopeEffect( + VibrationEffect.Composed effect) throws XmlSerializerException { + SerializedWaveformEnvelopeEffect.Builder builder = + new SerializedWaveformEnvelopeEffect.Builder(); + List<VibrationEffectSegment> segments = effect.getSegments(); + XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1, + "Unsupported repeating waveform envelope effect %s", effect); + for (int i = 0; i < segments.size(); i++) { + XmlValidator.checkSerializerCondition(segments.get(i) instanceof PwleSegment, + "Unsupported segment for waveform envelope effect %s", segments.get(i)); + PwleSegment segment = (PwleSegment) segments.get(i); + + if (i == 0 && segment.getStartFrequencyHz() != segment.getEndFrequencyHz()) { + // Initial frequency explicitly defined. + builder.setInitialFrequencyHz(segment.getStartFrequencyHz()); + } + + builder.addControlPoint(segment.getEndAmplitude(), segment.getEndFrequencyHz(), + segment.getDuration()); + } + + return new SerializedComposedEffect(builder.build()); + } + + private static SerializedComposedEffect serializeBasicEnvelopeEffect( + VibrationEffect.Composed effect) throws XmlSerializerException { + SerializedBasicEnvelopeEffect.Builder builder = new SerializedBasicEnvelopeEffect.Builder(); + List<VibrationEffectSegment> segments = effect.getSegments(); + XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1, + "Unsupported repeating basic envelope effect %s", effect); + for (int i = 0; i < segments.size(); i++) { + XmlValidator.checkSerializerCondition(segments.get(i) instanceof BasicPwleSegment, + "Unsupported segment for basic envelope effect %s", segments.get(i)); + BasicPwleSegment segment = (BasicPwleSegment) segments.get(i); + + if (i == 0 && segment.getStartSharpness() != segment.getEndSharpness()) { + // Initial sharpness explicitly defined. + builder.setInitialSharpness(segment.getStartSharpness()); + } + + builder.addControlPoint(segment.getEndIntensity(), segment.getEndSharpness(), + segment.getDuration()); + } + + return new SerializedComposedEffect(builder.build()); + } + private static SerializedComposedEffect serializeWaveformEffect( VibrationEffect.Composed effect) throws XmlSerializerException { SerializedAmplitudeStepWaveform.Builder serializedWaveformBuilder = diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java index 4122215a2b04..df262cfecd5a 100644 --- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java +++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java @@ -42,14 +42,22 @@ public final class XmlConstants { public static final String TAG_PREDEFINED_EFFECT = "predefined-effect"; public static final String TAG_PRIMITIVE_EFFECT = "primitive-effect"; public static final String TAG_VENDOR_EFFECT = "vendor-effect"; + public static final String TAG_WAVEFORM_ENVELOPE_EFFECT = "waveform-envelope-effect"; + public static final String TAG_BASIC_ENVELOPE_EFFECT = "basic-envelope-effect"; public static final String TAG_WAVEFORM_EFFECT = "waveform-effect"; public static final String TAG_WAVEFORM_ENTRY = "waveform-entry"; public static final String TAG_REPEATING = "repeating"; + public static final String TAG_CONTROL_POINT = "control-point"; 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_FREQUENCY_HZ = "frequencyHz"; + public static final String ATTRIBUTE_INITIAL_FREQUENCY_HZ = "initialFrequencyHz"; + public static final String ATTRIBUTE_INTENSITY = "intensity"; + public static final String ATTRIBUTE_SHARPNESS = "sharpness"; + public static final String ATTRIBUTE_INITIAL_SHARPNESS = "initialSharpness"; public static final String ATTRIBUTE_SCALE = "scale"; public static final String ATTRIBUTE_DELAY_MS = "delayMs"; public static final String ATTRIBUTE_DELAY_TYPE = "delayType"; diff --git a/core/java/com/android/internal/vibrator/persistence/XmlReader.java b/core/java/com/android/internal/vibrator/persistence/XmlReader.java index 0ac6fefc8cb2..1c4a783f0fe4 100644 --- a/core/java/com/android/internal/vibrator/persistence/XmlReader.java +++ b/core/java/com/android/internal/vibrator/persistence/XmlReader.java @@ -221,12 +221,63 @@ public final class XmlReader { if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) { return defaultValue; } + + return readAttributeFloatInRange(parser, attrName, lowerInclusive, upperInclusive); + } + + /** + * Read attribute from current tag as a float within given inclusive range. + */ + public static float readAttributeFloatInRange( + TypedXmlPullParser parser, String attrName, float lowerInclusive, + float upperInclusive) throws XmlParserException { String tagName = parser.getName(); 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); + "Unexpected %s = %f in tag %s, expected %s in [%f, %f]", attrName, value, tagName, + attrName, lowerInclusive, upperInclusive); + return value; + } + + /** + * Read attribute from current tag as a positive float, returning default value if attribute + * is missing. + */ + public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName, + float defaultValue) throws XmlParserException { + if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) { + return defaultValue; + } + + return readAttributePositiveFloat(parser, attrName); + } + + /** + * Read attribute from current tag as a positive float. + */ + public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName) + throws XmlParserException { + String tagName = parser.getName(); + float value = readAttributeFloat(parser, attrName); + + XmlValidator.checkParserCondition(value > 0, + "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName, + attrName); + return value; + } + + /** + * Read attribute from current tag as a positive long. + */ + public static long readAttributePositiveLong(TypedXmlPullParser parser, String attrName) + throws XmlParserException { + String tagName = parser.getName(); + long value = readAttributeLong(parser, attrName); + + XmlValidator.checkParserCondition(value > 0, + "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName, + attrName); return value; } @@ -251,4 +302,15 @@ public final class XmlReader { throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e); } } + + private static long readAttributeLong(TypedXmlPullParser parser, String attrName) + throws XmlParserException { + String tagName = parser.getName(); + try { + return parser.getAttributeLong(NAMESPACE, attrName); + } catch (XmlPullParserException e) { + String rawValue = parser.getAttributeValue(NAMESPACE, attrName); + throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e); + } + } } diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java index 40710577b154..5f25e9315831 100644 --- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java @@ -439,6 +439,498 @@ public class VibrationEffectXmlSerializationTest { } @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testWaveformEnvelopeEffect_allSucceed() throws Exception { + VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder() + .addControlPoint(0.2f, 80f, 10) + .addControlPoint(0.5f, 150f, 10) + .build(); + + String xml = """ + <vibration-effect> + <waveform-envelope-effect> + <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" /> + <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" /> + </waveform-envelope-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, xml); + assertPublicApisRoundTrip(effect); + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, xml); + assertHiddenApisRoundTrip(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testWaveformEnvelopeEffectWithInitialFrequency_allSucceed() throws Exception { + VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder() + .setInitialFrequencyHz(20) + .addControlPoint(0.2f, 80f, 10) + .addControlPoint(0.5f, 150f, 10) + .build(); + + String xml = """ + <vibration-effect> + <waveform-envelope-effect initialFrequencyHz="20.0"> + <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" /> + <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" /> + </waveform-envelope-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, xml); + assertPublicApisRoundTrip(effect); + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, xml); + assertHiddenApisRoundTrip(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testWaveformEnvelopeEffect_badXml_throwsException() throws IOException { + // Incomplete XML + assertParseElementFails(""" + <vibration-effect> + <waveform-envelope-effect> + <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" /> + </vibration-effect> + """); + assertParseElementFails(""" + <vibration-effect> + <waveform-envelope-effect> + <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10"> + </waveform-envelope-effect> + </vibration-effect> + """); + assertParseElementFails(""" + <vibration-effect> + <waveform-envelope-effect> + <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" /> + </waveform-envelope-effect> + """); + + // Bad vibration XML + assertParseElementFails(""" + <vibration-effect> + <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" /> + </waveform-envelope-effect> + </vibration-effect> + """); + + // "waveform-envelope-effect" tag with invalid attributes + assertParseElementFails(""" + <vibration-effect> + <waveform-envelope-effect init_freq="20.0"> + <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" /> + </waveform-envelope-effect> + </vibration-effect> + """); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testWaveformEnvelopeEffect_noControlPoints_allFail() throws IOException { + String xml = "<vibration-effect><waveform-envelope-effect/></vibration-effect>"; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = "<vibration-effect><waveform-envelope-effect> \n " + + "</waveform-envelope-effect></vibration-effect>"; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = "<vibration-effect><waveform-envelope-effect>invalid</waveform-envelope-effect" + + "></vibration-effect>"; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <waveform-envelope-effect> + <control-point /> + </waveform-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <waveform-envelope-effect initialFrequencyHz="20.0" /> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <waveform-envelope-effect initialFrequencyHz="20.0"> \n </waveform-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <waveform-envelope-effect initialFrequencyHz="20.0"> + invalid + </waveform-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <waveform-envelope-effect initialFrequencyHz="20.0"> + <control-point /> + </waveform-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testWaveformEnvelopeEffect_badControlPointData_allFail() throws IOException { + String xml = """ + <vibration-effect> + <waveform-envelope-effect> + <control-point amplitude="-1" frequencyHz="80.0" durationMs="100" /> + </waveform-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <waveform-envelope-effect> + <control-point amplitude="0.2" frequencyHz="0" durationMs="100" /> + </waveform-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <waveform-envelope-effect initialFrequencyHz="0"> + <control-point amplitude="0.2" frequencyHz="30" durationMs="100" /> + </waveform-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <waveform-envelope-effect> + <control-point amplitude="0.2" frequencyHz="80.0" durationMs="0" /> + </waveform-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <waveform-envelope-effect> + <control-point amplitude="0.2" /> + </waveform-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + } + + @Test + @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testWaveformEnvelopeEffect_featureFlagDisabled_allFail() throws Exception { + VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder() + .setInitialFrequencyHz(20) + .addControlPoint(0.2f, 80f, 10) + .addControlPoint(0.5f, 150f, 10) + .build(); + + String xml = """ + <vibration-effect> + <waveform-envelope-effect initialFrequencyHz="20.0"> + <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" /> + <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" /> + </waveform-envelope-effect> + </vibration-effect> + """; + + assertPublicApisParserFails(xml); + assertPublicApisSerializerFails(effect); + assertHiddenApisParserFails(xml); + assertHiddenApisSerializerFails(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testBasicEnvelopeEffect_allSucceed() throws Exception { + VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder() + .addControlPoint(0.2f, 0.5f, 10) + .addControlPoint(0.0f, 1f, 10) + .build(); + + String xml = """ + <vibration-effect> + <basic-envelope-effect> + <control-point intensity="0.2" sharpness="0.5" durationMs="10" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, xml); + assertPublicApisRoundTrip(effect); + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, xml); + assertHiddenApisRoundTrip(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testBasicEnvelopeEffectWithInitialSharpness_allSucceed() throws Exception { + VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder() + .setInitialSharpness(0.3f) + .addControlPoint(0.2f, 0.5f, 10) + .addControlPoint(0.0f, 1f, 10) + .build(); + + String xml = """ + <vibration-effect> + <basic-envelope-effect initialSharpness="0.3"> + <control-point intensity="0.2" sharpness="0.5" durationMs="10" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, xml); + assertPublicApisRoundTrip(effect); + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, xml); + assertHiddenApisRoundTrip(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testBasicEnvelopeEffect_badXml_throwsException() throws IOException { + // Incomplete XML + assertParseElementFails(""" + <vibration-effect> + <basic-envelope-effect> + <control-point intensity="0.2" sharpness="0.8" durationMs="10" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </vibration-effect> + """); + assertParseElementFails(""" + <vibration-effect> + <basic-envelope-effect> + <control-point intensity="0.2" sharpness="0.8" durationMs="10"> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """); + assertParseElementFails(""" + <vibration-effect> + <basic-envelope-effect> + <control-point intensity="0.2" sharpness="0.8" durationMs="10" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + """); + + // Bad vibration XML + assertParseElementFails(""" + <vibration-effect> + <control-point intensity="0.2" sharpness="0.8" durationMs="10" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """); + + // "basic-envelope-effect" tag with invalid attributes + assertParseElementFails(""" + <vibration-effect> + <basic-envelope-effect init_sharp="20.0"> + <control-point intensity="0.2" sharpness="0.8" durationMs="10" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testBasicEnvelopeEffect_noControlPoints_allFail() throws IOException { + String xml = "<vibration-effect><basic-envelope-effect/></vibration-effect>"; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = "<vibration-effect><basic-envelope-effect> \n " + + "</basic-envelope-effect></vibration-effect>"; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = "<vibration-effect><basic-envelope-effect>invalid</basic-envelope-effect" + + "></vibration-effect>"; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect> + <control-point /> + </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect initialSharpness="0.2" /> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect initialSharpness="0.2"> \n </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect initialSharpness="0.2"> + invalid + </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect initialSharpness="0.2"> + <control-point /> + </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testBasicEnvelopeEffect_badControlPointData_allFail() throws IOException { + String xml = """ + <vibration-effect> + <basic-envelope-effect> + <control-point intensity="-1" sharpness="0.8" durationMs="100" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect> + <control-point intensity="0.2" sharpness="-1" durationMs="100" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect initialSharpness="-1.0"> + <control-point intensity="0.2" sharpness="0.8" durationMs="0" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect initialSharpness="2.0"> + <control-point intensity="0.2" sharpness="0.8" durationMs="0" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect> + <control-point intensity="0.2" sharpness="0.8" durationMs="10" /> + <control-point intensity="0.5" sharpness="0.8" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + + xml = """ + <vibration-effect> + <basic-envelope-effect> + <control-point intensity="0.2" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """; + assertPublicApisParserFails(xml); + assertHiddenApisParserFails(xml); + } + + @Test + @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testBasicEnvelopeEffect_featureFlagDisabled_allFail() throws Exception { + VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder() + .setInitialSharpness(0.3f) + .addControlPoint(0.2f, 0.5f, 10) + .addControlPoint(0.0f, 1f, 10) + .build(); + + String xml = """ + <vibration-effect> + <basic-envelope-effect initialSharpness="0.3"> + <control-point intensity="0.2" sharpness="0.5" durationMs="10" /> + <control-point intensity="0.0" sharpness="1.0" durationMs="10" /> + </basic-envelope-effect> + </vibration-effect> + """; + + assertPublicApisParserFails(xml); + assertPublicApisSerializerFails(effect); + + assertHiddenApisParserFails(xml); + assertHiddenApisSerializerFails(effect); + } + + @Test @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void testVendorEffect_allSucceed() throws Exception { PersistableBundle vendorData = new PersistableBundle(); diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt index b4148d657b0d..29f8d199c1d1 100644 --- a/core/xsd/vibrator/vibration/schema/current.txt +++ b/core/xsd/vibrator/vibration/schema/current.txt @@ -1,6 +1,23 @@ // Signature format: 2.0 package com.android.internal.vibrator.persistence { + public class BasicControlPoint { + ctor public BasicControlPoint(); + method public long getDurationMs(); + method public float getIntensity(); + method public float getSharpness(); + method public void setDurationMs(long); + method public void setIntensity(float); + method public void setSharpness(float); + } + + public class BasicEnvelopeEffect { + ctor public BasicEnvelopeEffect(); + method public java.util.List<com.android.internal.vibrator.persistence.BasicControlPoint> getControlPoint(); + method public float getInitialSharpness(); + method public void setInitialSharpness(float); + } + public class PredefinedEffect { ctor public PredefinedEffect(); method public com.android.internal.vibrator.persistence.PredefinedEffectName getName(); @@ -47,14 +64,18 @@ package com.android.internal.vibrator.persistence { public class VibrationEffect { ctor public VibrationEffect(); + method public com.android.internal.vibrator.persistence.BasicEnvelopeEffect getBasicEnvelopeEffect_optional(); method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional(); method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional(); method public byte[] getVendorEffect_optional(); method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional(); + method public com.android.internal.vibrator.persistence.WaveformEnvelopeEffect getWaveformEnvelopeEffect_optional(); + method public void setBasicEnvelopeEffect_optional(com.android.internal.vibrator.persistence.BasicEnvelopeEffect); method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect); method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect); method public void setVendorEffect_optional(byte[]); method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect); + method public void setWaveformEnvelopeEffect_optional(com.android.internal.vibrator.persistence.WaveformEnvelopeEffect); } public class VibrationSelect { @@ -67,6 +88,16 @@ package com.android.internal.vibrator.persistence { enum_constant public static final com.android.internal.vibrator.persistence.WaveformAmplitudeDefault _default; } + public class WaveformControlPoint { + ctor public WaveformControlPoint(); + method public float getAmplitude(); + method public long getDurationMs(); + method public float getFrequencyHz(); + method public void setAmplitude(float); + method public void setDurationMs(long); + method public void setFrequencyHz(float); + } + public class WaveformEffect { ctor public WaveformEffect(); method public com.android.internal.vibrator.persistence.WaveformEffect.Repeating getRepeating(); @@ -87,6 +118,13 @@ package com.android.internal.vibrator.persistence { method public void setDurationMs(java.math.BigInteger); } + public class WaveformEnvelopeEffect { + ctor public WaveformEnvelopeEffect(); + method public java.util.List<com.android.internal.vibrator.persistence.WaveformControlPoint> getControlPoint(); + method public float getInitialFrequencyHz(); + method public void setInitialFrequencyHz(float); + } + public class XmlParser { ctor public XmlParser(); method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd index 910a9b700b5c..b4df2d187702 100644 --- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd +++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd @@ -54,6 +54,12 @@ <xs:element name="primitive-effect" type="PrimitiveEffect"/> </xs:sequence> + <!-- Waveform envelope effect --> + <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect"/> + + <!-- Basic envelope effect --> + <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/> + </xs:choice> </xs:complexType> @@ -180,4 +186,54 @@ </xs:restriction> </xs:simpleType> + <!-- Definition of a waveform envelope effect --> + <xs:complexType name="WaveformEnvelopeEffect"> + <xs:sequence> + <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1" + type="WaveformControlPoint" /> + </xs:sequence> + <xs:attribute name="initialFrequencyHz" type="ControlPointFrequency" /> + </xs:complexType> + + <!-- Definition of a basic envelope effect --> + <xs:complexType name="BasicEnvelopeEffect"> + <xs:sequence> + <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1" + type="BasicControlPoint" /> + </xs:sequence> + <xs:attribute name="initialSharpness" type="NormalizedControlPointUnit" /> + </xs:complexType> + + <xs:complexType name="WaveformControlPoint"> + <xs:attribute name="amplitude" type="NormalizedControlPointUnit" use="required"/> + <xs:attribute name="frequencyHz" type="ControlPointFrequency" use="required"/> + <xs:attribute name="durationMs" type="PositiveLong" use="required"/> + </xs:complexType> + + <xs:complexType name="BasicControlPoint"> + <xs:attribute name="intensity" type="NormalizedControlPointUnit" use="required"/> + <xs:attribute name="sharpness" type="NormalizedControlPointUnit" use="required"/> + <xs:attribute name="durationMs" type="PositiveLong" use="required"/> + </xs:complexType> + + <xs:simpleType name="ControlPointFrequency"> + <xs:restriction base="xs:float"> + <xs:minExclusive value="0"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="PositiveLong"> + <xs:restriction base="xs:long"> + <xs:minExclusive value="0"/> + </xs:restriction> + </xs:simpleType> + + <!-- Normalized control point unit float in [0,1] --> + <xs:simpleType name="NormalizedControlPointUnit"> + <xs:restriction base="xs:float"> + <xs:minInclusive value="0"/> + <xs:maxInclusive value="1"/> + </xs:restriction> + </xs:simpleType> + </xs:schema> diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd index 3c8e01605659..fba966faa9c9 100644 --- a/core/xsd/vibrator/vibration/vibration.xsd +++ b/core/xsd/vibrator/vibration/vibration.xsd @@ -52,6 +52,12 @@ <xs:element name="primitive-effect" type="PrimitiveEffect"/> </xs:sequence> + <!-- Waveform envelope effect --> + <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect"/> + + <!-- Basic envelope effect --> + <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/> + </xs:choice> </xs:complexType> @@ -157,4 +163,54 @@ </xs:restriction> </xs:simpleType> + <!-- Definition of a waveform envelope effect --> + <xs:complexType name="WaveformEnvelopeEffect"> + <xs:sequence> + <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1" + type="WaveformControlPoint" /> + </xs:sequence> + <xs:attribute name="initialFrequencyHz" type="ControlPointFrequency" /> + </xs:complexType> + + <!-- Definition of a basic envelope effect --> + <xs:complexType name="BasicEnvelopeEffect"> + <xs:sequence> + <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1" + type="BasicControlPoint" /> + </xs:sequence> + <xs:attribute name="initialSharpness" type="NormalizedControlPointUnit" /> + </xs:complexType> + + <xs:complexType name="WaveformControlPoint"> + <xs:attribute name="amplitude" type="NormalizedControlPointUnit" use="required"/> + <xs:attribute name="frequencyHz" type="ControlPointFrequency" use="required"/> + <xs:attribute name="durationMs" type="PositiveLong" use="required"/> + </xs:complexType> + + <xs:complexType name="BasicControlPoint"> + <xs:attribute name="intensity" type="NormalizedControlPointUnit" use="required"/> + <xs:attribute name="sharpness" type="NormalizedControlPointUnit" use="required"/> + <xs:attribute name="durationMs" type="PositiveLong" use="required"/> + </xs:complexType> + + <xs:simpleType name="ControlPointFrequency"> + <xs:restriction base="xs:float"> + <xs:minExclusive value="0"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="PositiveLong"> + <xs:restriction base="xs:long"> + <xs:minExclusive value="0"/> + </xs:restriction> + </xs:simpleType> + + <!-- Normalized control point unit float in [0,1] --> + <xs:simpleType name="NormalizedControlPointUnit"> + <xs:restriction base="xs:float"> + <xs:minInclusive value="0"/> + <xs:maxInclusive value="1"/> + </xs:restriction> + </xs:simpleType> + </xs:schema> |