Merge "[13/n] Implement MixedLetterboxController" into main
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 0000000..a090c7a
--- /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 0000000..6a89343
--- /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 a9fbcaf..314bfe4 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 @@
* }
* </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 @@
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 cb834a5..ebe3434 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 @@
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 @@
* <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 @@
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 @@
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 4122215..df262cf 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 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 0ac6fef..1c4a783 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 @@
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 @@
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 4071057..5f25e93 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 @@
}
@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 b4148d6..29f8d19 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 @@
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 @@
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 @@
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 910a9b7..b4df2d1 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 3c8e016..fba966f 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>
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index d993b87..28d96e3 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -28,12 +28,14 @@
#define INDENT " "
#define INDENT2 " "
+namespace android {
+
namespace {
+
// Time to spend fading out the pointer completely.
const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
-} // namespace
-namespace android {
+} // namespace
// --- MouseCursorController ---
@@ -64,17 +66,23 @@
mLocked.pointerSprite.clear();
}
-void MouseCursorController::move(float deltaX, float deltaY) {
+FloatPoint MouseCursorController::move(float deltaX, float deltaY) {
#if DEBUG_MOUSE_CURSOR_UPDATES
ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
#endif
if (deltaX == 0.0f && deltaY == 0.0f) {
- return;
+ return {0, 0};
}
+ // When transition occurs, the MouseCursorController object may or may not be deleted, depending
+ // if there's another display on the other side of the transition. At this point we still need
+ // to move the cursor to the boundary.
std::scoped_lock lock(mLock);
-
- setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+ const FloatPoint position{mLocked.pointerX + deltaX, mLocked.pointerY + deltaY};
+ setPositionLocked(position.x, position.y);
+ // The amount of the delta that was not consumed as a result of the cursor
+ // hitting the edge of the display.
+ return {position.x - mLocked.pointerX, position.y - mLocked.pointerY};
}
void MouseCursorController::setPosition(float x, float y) {
@@ -85,19 +93,23 @@
setPositionLocked(x, y);
}
-void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
- const auto& v = mLocked.viewport;
- if (!v.isValid()) return;
-
+FloatRect MouseCursorController::getBoundsLocked() REQUIRES(mLock) {
// The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside
// the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily
// close to the outside edge.
- const FloatRect bounds{
+ return FloatRect{
static_cast<float>(mLocked.viewport.logicalLeft),
static_cast<float>(mLocked.viewport.logicalTop),
static_cast<float>(mLocked.viewport.logicalRight - 1),
static_cast<float>(mLocked.viewport.logicalBottom - 1),
};
+}
+
+void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
+ const auto& v = mLocked.viewport;
+ if (!v.isValid()) return;
+
+ const FloatRect bounds = getBoundsLocked();
mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x));
mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y));
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 12b31a8..e14a55d 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -20,9 +20,6 @@
#include <gui/DisplayEventReceiver.h>
#include <input/DisplayViewport.h>
#include <input/Input.h>
-#include <utils/BitSet.h>
-#include <utils/Looper.h>
-#include <utils/RefBase.h>
#include <functional>
#include <map>
@@ -43,7 +40,8 @@
MouseCursorController(PointerControllerContext& context);
~MouseCursorController();
- void move(float deltaX, float deltaY);
+ // Move the pointer and return unconsumed delta
+ FloatPoint move(float deltaX, float deltaY);
void setPosition(float x, float y);
FloatPoint getPosition() const;
ui::LogicalDisplayId getDisplayId() const;
@@ -113,6 +111,7 @@
bool doFadingAnimationLocked(nsecs_t timestamp);
void startAnimationLocked();
+ FloatRect getBoundsLocked();
};
} // namespace android
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 78d7d3a..a713f1d 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -138,15 +138,19 @@
return mDisplayInfoListener->mLock;
}
-void PointerController::move(float deltaX, float deltaY) {
+FloatPoint PointerController::move(float deltaX, float deltaY) {
const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
- vec2 transformed;
+ ui::Transform transform;
{
std::scoped_lock lock(getLock());
- const auto& transform = getTransformForDisplayLocked(displayId);
- transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+ transform = getTransformForDisplayLocked(displayId);
}
- mCursorController.move(transformed.x, transformed.y);
+
+ const vec2 transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+
+ const FloatPoint unconsumedDelta = mCursorController.move(transformed.x, transformed.y);
+ return FloatPoint(transformWithoutTranslation(transform.inverse(),
+ {unconsumedDelta.x, unconsumedDelta.y}));
}
void PointerController::setPosition(float x, float y) {
@@ -295,6 +299,11 @@
mCursorController.setSkipScreenshot(false);
}
+ui::Transform PointerController::getDisplayTransform() const {
+ std::scoped_lock lock(getLock());
+ return getTransformForDisplayLocked(mLocked.pointerDisplayId);
+}
+
void PointerController::doInactivityTimeout() {
fade(Transition::GRADUAL);
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index ee8d121..8b33190 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -51,7 +51,7 @@
~PointerController() override;
- void move(float deltaX, float deltaY) override;
+ FloatPoint move(float deltaX, float deltaY) override;
void setPosition(float x, float y) override;
FloatPoint getPosition() const override;
ui::LogicalDisplayId getDisplayId() const override;
@@ -67,6 +67,7 @@
void setCustomPointerIcon(const SpriteIcon& icon) override;
void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
void clearSkipScreenshotFlags() override;
+ ui::Transform getDisplayTransform() const override;
virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void doInactivityTimeout();
@@ -165,7 +166,7 @@
~TouchPointerController() override;
- void move(float, float) override {
+ FloatPoint move(float, float) override {
LOG_ALWAYS_FATAL("Should not be called");
}
void setPosition(float, float) override {
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 5b00fca..80c934a 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -40,6 +40,8 @@
CURSOR_TYPE_CUSTOM = -1,
};
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
using ::testing::AllOf;
using ::testing::Field;
using ::testing::NiceMock;
@@ -399,6 +401,135 @@
testing::Values(PointerControllerInterface::ControllerType::MOUSE,
PointerControllerInterface::ControllerType::STYLUS));
+class MousePointerControllerTest : public PointerControllerTest {
+protected:
+ MousePointerControllerTest() {
+ sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>);
+ EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite));
+
+ // create a mouse pointer controller
+ mPointerController =
+ PointerController::create(mPolicy, mLooper, *mSpriteController,
+ PointerControllerInterface::ControllerType::MOUSE);
+
+ // set display viewport
+ DisplayViewport viewport;
+ viewport.displayId = ui::LogicalDisplayId::DEFAULT;
+ viewport.logicalRight = 5;
+ viewport.logicalBottom = 5;
+ viewport.physicalRight = 5;
+ viewport.physicalBottom = 5;
+ viewport.deviceWidth = 5;
+ viewport.deviceHeight = 5;
+ mPointerController->setDisplayViewport(viewport);
+ }
+};
+
+struct MousePointerControllerTestParam {
+ vec2 startPosition;
+ vec2 delta;
+ vec2 endPosition;
+ vec2 unconsumedDelta;
+};
+
+class PointerControllerViewportTransitionTest
+ : public MousePointerControllerTest,
+ public testing::WithParamInterface<MousePointerControllerTestParam> {};
+
+TEST_P(PointerControllerViewportTransitionTest, testPointerViewportTransition) {
+ const auto& params = GetParam();
+
+ mPointerController->setPosition(params.startPosition.x, params.startPosition.y);
+ auto unconsumedDelta = mPointerController->move(params.delta.x, params.delta.y);
+
+ auto position = mPointerController->getPosition();
+ EXPECT_NEAR(position.x, params.endPosition.x, EPSILON);
+ EXPECT_NEAR(position.y, params.endPosition.y, EPSILON);
+ EXPECT_NEAR(unconsumedDelta.x, params.unconsumedDelta.x, EPSILON);
+ EXPECT_NEAR(unconsumedDelta.y, params.unconsumedDelta.y, EPSILON);
+}
+
+INSTANTIATE_TEST_SUITE_P(PointerControllerViewportTransitionTest,
+ PointerControllerViewportTransitionTest,
+ testing::Values(
+ // no transition
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {2.0f, 2.0f},
+ {4.0f, 4.0f},
+ {0.0f, 0.0f}},
+ // right boundary
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, 0.0f},
+ {4.0f, 2.0f},
+ {1.0f, 0.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, -1.0f},
+ {4.0f, 1.0f},
+ {1.0f, 0.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, 1.0f},
+ {4.0f, 3.0f},
+ {1.0f, 0.0f}},
+ // left boundary
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, 0.0f},
+ {0.0f, 2.0f},
+ {-1.0f, 0.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, -1.0f},
+ {0.0f, 1.0f},
+ {-1.0f, 0.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, 1.0f},
+ {0.0f, 3.0f},
+ {-1.0f, 0.0f}},
+ // bottom boundary
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {0.0f, 3.0f},
+ {2.0f, 4.0f},
+ {0.0f, 1.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-1.0f, 3.0f},
+ {1.0f, 4.0f},
+ {0.0f, 1.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {1.0f, 3.0f},
+ {3.0f, 4.0f},
+ {0.0f, 1.0f}},
+ // top boundary
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {0.0f, -3.0f},
+ {2.0f, 0.0f},
+ {0.0f, -1.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-1.0f, -3.0f},
+ {1.0f, 0.0f},
+ {0.0f, -1.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {1.0f, -3.0f},
+ {3.0f, 0.0f},
+ {0.0f, -1.0f}},
+ // top-left corner
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, -3.0f},
+ {0.0f, 0.0f},
+ {-1.0f, -1.0f}},
+ // top-right corner
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, -3.0f},
+ {4.0f, 0.0f},
+ {1.0f, -1.0f}},
+ // bottom-right corner
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, 3.0f},
+ {4.0f, 4.0f},
+ {1.0f, 1.0f}},
+ // bottom-left corner
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, 3.0f},
+ {0.0f, 4.0f},
+ {-1.0f, 1.0f}}));
+
class PointerControllerWindowInfoListenerTest : public Test {};
TEST_F(PointerControllerWindowInfoListenerTest,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index eff5fc0..d8fc52b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -216,20 +216,13 @@
"handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " +
event.keycodes.contentToString()
}
- if (
- event.keycodes.contains(KEYCODE_N) &&
- event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)
- ) {
- debugLog { "Note task triggered by keyboard shortcut" }
- backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
- return true
- }
if (event.keycodes.size == 1 && event.keycodes[0] == KEYCODE_STYLUS_BUTTON_TAIL) {
debugLog { "Note task triggered by stylus tail button" }
backgroundExecutor.execute { controller.showNoteTask(TAIL_BUTTON) }
return true
}
- return false
+ backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
+ return true
}
private fun isKeyGestureSupported(gestureType: Int): Boolean {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index d88b758..266cb51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -181,11 +181,9 @@
@Test
@EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
- fun handlesShortcut_metaCtrlN() {
+ fun handlesShortcut_keyGestureTypeOpenNotes() {
val gestureEvent =
KeyGestureEvent.Builder()
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_N))
- .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
.setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)
.setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
.build()