Loading core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * 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); } } } core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * 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); } } } core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java +40 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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); Loading core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java +57 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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); } Loading Loading @@ -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 = Loading core/java/com/android/internal/vibrator/persistence/XmlConstants.java +8 −0 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading
core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * 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); } } }
core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * 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); } } }
core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java +40 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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); Loading
core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java +57 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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); } Loading Loading @@ -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 = Loading
core/java/com/android/internal/vibrator/persistence/XmlConstants.java +8 −0 Original line number Diff line number Diff line Loading @@ -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"; Loading