Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 65fc75c0 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "XML support for vibration primitive delay type" into main

parents 05d83848 f9f19cda
Loading
Loading
Loading
Loading
+49 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.internal.vibrator.persistence;

import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DELAY_MS;
import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DELAY_TYPE;
import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_NAME;
import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_SCALE;
import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
@@ -25,9 +26,11 @@ import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITI
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.VibrationEffect;
import android.os.vibrator.Flags;
import android.os.vibrator.PrimitiveSegment;

import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveDelayType;
import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -46,18 +49,27 @@ final class SerializedCompositionPrimitive implements SerializedSegment {
    private final PrimitiveEffectName mPrimitiveName;
    private final float mPrimitiveScale;
    private final int mPrimitiveDelayMs;
    @Nullable
    private final PrimitiveDelayType mDelayType;

    SerializedCompositionPrimitive(PrimitiveEffectName primitiveName, float scale, int delayMs) {
    SerializedCompositionPrimitive(PrimitiveEffectName primitiveName, float scale, int delayMs,
            @Nullable PrimitiveDelayType delayType) {
        mPrimitiveName = primitiveName;
        mPrimitiveScale = scale;
        mPrimitiveDelayMs = delayMs;
        mDelayType = delayType;
    }

    @Override
    public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
        if (Flags.primitiveCompositionAbsoluteDelay() && mDelayType != null) {
            composition.addPrimitive(mPrimitiveName.getPrimitiveId(), mPrimitiveScale,
                    mPrimitiveDelayMs, mDelayType.getDelayType());
        } else {
            composition.addPrimitive(mPrimitiveName.getPrimitiveId(), mPrimitiveScale,
                    mPrimitiveDelayMs);
        }
    }

    @Override
    public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
@@ -72,6 +84,12 @@ final class SerializedCompositionPrimitive implements SerializedSegment {
            serializer.attributeInt(NAMESPACE, ATTRIBUTE_DELAY_MS, mPrimitiveDelayMs);
        }

        if (Flags.primitiveCompositionAbsoluteDelay() && mDelayType != null) {
            if (mDelayType.getDelayType() != PrimitiveSegment.DEFAULT_DELAY_TYPE) {
                serializer.attribute(NAMESPACE, ATTRIBUTE_DELAY_TYPE, mDelayType.toString());
            }
        }

        serializer.endTag(NAMESPACE, TAG_PRIMITIVE_EFFECT);
    }

@@ -81,6 +99,7 @@ final class SerializedCompositionPrimitive implements SerializedSegment {
                + "name=" + mPrimitiveName
                + ", scale=" + mPrimitiveScale
                + ", delayMs=" + mPrimitiveDelayMs
                + ", delayType=" + mDelayType
                + '}';
    }

@@ -91,8 +110,14 @@ final class SerializedCompositionPrimitive implements SerializedSegment {
        static SerializedCompositionPrimitive parseNext(@NonNull TypedXmlPullParser parser)
                throws XmlParserException, IOException {
            XmlValidator.checkStartTag(parser, TAG_PRIMITIVE_EFFECT);

            if (Flags.primitiveCompositionAbsoluteDelay()) {
                XmlValidator.checkTagHasNoUnexpectedAttributes(parser,
                        ATTRIBUTE_NAME, ATTRIBUTE_DELAY_MS, ATTRIBUTE_SCALE, ATTRIBUTE_DELAY_TYPE);
            } else {
                XmlValidator.checkTagHasNoUnexpectedAttributes(parser,
                        ATTRIBUTE_NAME, ATTRIBUTE_DELAY_MS, ATTRIBUTE_SCALE);
            }

            PrimitiveEffectName primitiveName = parsePrimitiveName(
                    parser.getAttributeValue(NAMESPACE, ATTRIBUTE_NAME));
@@ -100,11 +125,13 @@ final class SerializedCompositionPrimitive implements SerializedSegment {
                    parser, ATTRIBUTE_SCALE, 0, 1, PrimitiveSegment.DEFAULT_SCALE);
            int delayMs = XmlReader.readAttributeIntNonNegative(
                    parser, ATTRIBUTE_DELAY_MS, PrimitiveSegment.DEFAULT_DELAY_MILLIS);
            PrimitiveDelayType delayType = parseDelayType(
                    parser.getAttributeValue(NAMESPACE, ATTRIBUTE_DELAY_TYPE));

            // Consume tag
            XmlReader.readEndTag(parser);

            return new SerializedCompositionPrimitive(primitiveName, scale, delayMs);
            return new SerializedCompositionPrimitive(primitiveName, scale, delayMs, delayType);
        }

        @NonNull
@@ -119,5 +146,21 @@ final class SerializedCompositionPrimitive implements SerializedSegment {
            }
            return effectName;
        }

        @Nullable
        private static PrimitiveDelayType parseDelayType(@Nullable String name)
                throws XmlParserException {
            if (name == null) {
                return null;
            }
            if (!Flags.primitiveCompositionAbsoluteDelay()) {
                throw new XmlParserException("Unexpected primitive delay type " + name);
            }
            PrimitiveDelayType delayType = PrimitiveDelayType.findByName(name);
            if (delayType == null) {
                throw new XmlParserException("Unexpected primitive delay type " + name);
            }
            return delayType;
        }
    }
}
+14 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.os.vibrator.VibrationEffectSegment;

import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName;
import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveDelayType;
import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;

import java.util.List;
@@ -170,8 +171,20 @@ public final class VibrationEffectXmlSerializer {
        XmlValidator.checkSerializerCondition(primitiveName != null,
                "Unsupported primitive effect id %s", primitive.getPrimitiveId());

        PrimitiveDelayType delayType = null;

        if (Flags.primitiveCompositionAbsoluteDelay()) {
            delayType = PrimitiveDelayType.findByType(primitive.getDelayType());
            XmlValidator.checkSerializerCondition(delayType != null,
                    "Unsupported primitive delay type %s", primitive.getDelayType());
        } else {
            XmlValidator.checkSerializerCondition(
                    primitive.getDelayType() == PrimitiveSegment.DEFAULT_DELAY_TYPE,
                    "Unsupported primitive delay type %s", primitive.getDelayType());
        }

        return new SerializedCompositionPrimitive(
                primitiveName, primitive.getScale(), primitive.getDelay());
                primitiveName, primitive.getScale(), primitive.getDelay(), delayType);
    }

    private static int toAmplitudeInt(float amplitude) {
+52 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.VibrationEffect;
import android.os.VibrationEffect.Composition.DelayType;
import android.os.VibrationEffect.Composition.PrimitiveType;

import java.lang.annotation.Retention;
@@ -51,6 +52,7 @@ public final class XmlConstants {
    public static final String ATTRIBUTE_AMPLITUDE = "amplitude";
    public static final String ATTRIBUTE_SCALE = "scale";
    public static final String ATTRIBUTE_DELAY_MS = "delayMs";
    public static final String ATTRIBUTE_DELAY_TYPE = "delayType";

    public static final String VALUE_AMPLITUDE_DEFAULT = "default";

@@ -87,7 +89,7 @@ public final class XmlConstants {

        /**
         * Return the {@link PrimitiveEffectName} that represents given primitive id, or null if
         * none of the available names maps to the given id.
         * none of the available names map to the given id.
         */
        @Nullable
        public static PrimitiveEffectName findById(int primitiveId) {
@@ -200,4 +202,53 @@ public final class XmlConstants {
            return name().toLowerCase(Locale.ROOT);
        }
    }

    /** Represent supported values for attribute delay type in {@link #TAG_PRIMITIVE_EFFECT}  */
    public enum PrimitiveDelayType {
        PAUSE(VibrationEffect.Composition.DELAY_TYPE_PAUSE),
        RELATIVE_START_OFFSET(VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET);

        @DelayType private final int mDelayType;

        PrimitiveDelayType(@DelayType int type) {
            mDelayType = type;
        }

        /**
         * Return the {@link PrimitiveEffectName} that represents given primitive id, or null if
         * none of the available names maps to the given id.
         */
        @Nullable
        public static PrimitiveDelayType findByType(int delayType) {
            for (PrimitiveDelayType type : PrimitiveDelayType.values()) {
                if (type.mDelayType == delayType) {
                    return type;
                }
            }
            return null;
        }

        /**
         * Return the {@link PrimitiveEffectName} that represents given primitive name, or null if
         * none of the available names maps to the given name.
         */
        @Nullable
        public static PrimitiveDelayType findByName(@NonNull String delayType) {
            try {
                return PrimitiveDelayType.valueOf(delayType.toUpperCase(Locale.ROOT));
            } catch (IllegalArgumentException e) {
                return null;
            }
        }

        @DelayType
        public int getDelayType() {
            return mDelayType;
        }

        @Override
        public String toString() {
            return name().toLowerCase(Locale.ROOT);
        }
    }
}
+80 −2
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.os.vibrator.persistence;

import static android.os.VibrationEffect.Composition.DELAY_TYPE_PAUSE;
import static android.os.VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
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;
@@ -31,6 +33,7 @@ import android.os.PersistableBundle;
import android.os.VibrationEffect;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -437,7 +440,7 @@ public class VibrationEffectXmlSerializationTest {

    @Test
    @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
    public void testVendorEffect_featureFlagEnabled_allSucceed() throws Exception {
    public void testVendorEffect_allSucceed() throws Exception {
        PersistableBundle vendorData = new PersistableBundle();
        vendorData.putInt("id", 1);
        vendorData.putDouble("scale", 0.5);
@@ -476,7 +479,7 @@ public class VibrationEffectXmlSerializationTest {

    @Test
    @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
    public void testInvalidVendorEffect_featureFlagEnabled_allFail() throws IOException {
    public void testInvalidVendorEffect_allFail() throws IOException {
        String emptyTag = "<vibration-effect><vendor-effect/></vibration-effect>";
        assertPublicApisParserFails(emptyTag);
        assertHiddenApisParserFails(emptyTag);
@@ -526,6 +529,81 @@ public class VibrationEffectXmlSerializationTest {
        assertHiddenApisSerializerFails(vendorEffect);
    }

    @Test
    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
    public void testPrimitiveDelayType_allSucceed() throws Exception {
        VibrationEffect effect = VibrationEffect.startComposition()
                .addPrimitive(PRIMITIVE_TICK, 1.0f, 0, DELAY_TYPE_RELATIVE_START_OFFSET)
                .addPrimitive(PRIMITIVE_CLICK, 0.123f, 10, DELAY_TYPE_PAUSE)
                .compose();
        String xml = """
                <vibration-effect>
                    <primitive-effect name="tick" delayType="relative_start_offset"/>
                    <primitive-effect name="click" scale="0.123" delayMs="10"/>
                </vibration-effect>
                """;

        assertPublicApisParserSucceeds(xml, effect);
        assertPublicApisSerializerSucceeds(effect, "tick", "click");
        // Delay type pause is not serialized, as it's the default one
        assertPublicApisSerializerSucceeds(effect, "relative_start_offset", "click");
        assertPublicApisRoundTrip(effect);

        assertHiddenApisParserSucceeds(xml, effect);
        assertHiddenApisSerializerSucceeds(effect, "tick", "click");
        assertHiddenApisRoundTrip(effect);

        // Check PersistableBundle from round-trip
        VibrationEffect.Composed parsedEffect = ((VibrationEffect.Composed) parseVibrationEffect(
                serialize(effect), /* flags= */ 0));
        assertThat(parsedEffect.getRepeatIndex()).isEqualTo(-1);
        assertThat(parsedEffect.getSegments()).containsExactly(
                new PrimitiveSegment(PRIMITIVE_TICK, 1.0f, 0, DELAY_TYPE_RELATIVE_START_OFFSET),
                new PrimitiveSegment(PRIMITIVE_CLICK, 0.123f, 10, DELAY_TYPE_PAUSE))
                .inOrder();
    }

    @Test
    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
    public void testPrimitiveInvalidDelayType_allFail() {
        String emptyAttribute = """
                <vibration-effect>
                    <primitive-effect name="tick" delayType=""/>
                </vibration-effect>
                """;
        assertPublicApisParserFails(emptyAttribute);
        assertHiddenApisParserFails(emptyAttribute);

        String invalidString = """
                <vibration-effect>
                    <primitive-effect name="tick" delayType="invalid"/>
                </vibration-effect>
                """;
        assertPublicApisParserFails(invalidString);
        assertHiddenApisParserFails(invalidString);
    }

    @Test
    @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
    public void testPrimitiveDelayType_featureFlagDisabled_allFail() {
        VibrationEffect effect = VibrationEffect.startComposition()
                .addPrimitive(PRIMITIVE_TICK, 1.0f, 0, DELAY_TYPE_RELATIVE_START_OFFSET)
                .addPrimitive(PRIMITIVE_CLICK, 0.123f, 10, DELAY_TYPE_PAUSE)
                .compose();
        String xml = """
                <vibration-effect>
                    <primitive-effect name="tick" delayType="relative_start_offset"/>
                    <primitive-effect name="click" scale="0.123" delayMs="10" delayType="pause"/>
                </vibration-effect>
                """;

        assertPublicApisParserFails(xml);
        assertPublicApisSerializerFails(effect);

        assertHiddenApisParserFails(xml);
        assertHiddenApisSerializerFails(effect);
    }

    private void assertPublicApisParserFails(String xml) {
        assertThrows("Expected parseVibrationEffect to fail for " + xml,
                VibrationXmlParser.ParseFailedException.class,
+8 −0
Original line number Diff line number Diff line
@@ -15,12 +15,20 @@ package com.android.internal.vibrator.persistence {
    enum_constant public static final com.android.internal.vibrator.persistence.PredefinedEffectName tick;
  }

  public enum PrimitiveDelayType {
    method public String getRawName();
    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveDelayType pause;
    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveDelayType relative_start_offset;
  }

  public class PrimitiveEffect {
    ctor public PrimitiveEffect();
    method public java.math.BigInteger getDelayMs();
    method public com.android.internal.vibrator.persistence.PrimitiveDelayType getDelayType();
    method public com.android.internal.vibrator.persistence.PrimitiveEffectName getName();
    method public float getScale();
    method public void setDelayMs(java.math.BigInteger);
    method public void setDelayType(com.android.internal.vibrator.persistence.PrimitiveDelayType);
    method public void setName(com.android.internal.vibrator.persistence.PrimitiveEffectName);
    method public void setScale(float);
  }
Loading