summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ahmad Khalil <khalilahmad@google.com> 2024-11-01 16:12:44 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-11-01 16:12:44 +0000
commit3c9c6d3ef740a03650b8cb3bb2bc7b6fe93035d9 (patch)
treee2ba435eabc5eecfc0340be4a65412e30c4c8b04
parentce7e02273bdd074cdaba79f97a46fdd9263c958b (diff)
parentb563aba2740e79b89821e052061da6bc5732351c (diff)
Merge "Add initial frequency to envelope effects" into main
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/os/VibrationEffect.java36
-rw-r--r--core/java/android/os/vibrator/PwlePoint.java66
-rw-r--r--core/tests/vibrator/src/android/os/VibrationEffectTest.java68
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java37
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStats.java16
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorController.java16
-rw-r--r--services/core/jni/com_android_server_vibrator_VibratorController.cpp29
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java65
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java8
-rw-r--r--services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java37
11 files changed, 304 insertions, 75 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 39d086a8bd0f..fd2402a7a70d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -34531,6 +34531,7 @@ package android.os {
method public int describeContents();
method @NonNull public static android.os.VibrationEffect.Composition startComposition();
method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(@FloatRange(from=0) float);
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
field public static final int EFFECT_CLICK = 0; // 0x0
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index a3844e244edb..0cffd9f990fd 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -1740,7 +1740,9 @@ public abstract class VibrationEffect implements Parcelable {
*
* <p>The waveform envelope builder offers more flexibility for creating waveform effects,
* allowing control over vibration amplitude and frequency via smooth transitions between
- * values.
+ * values. The waveform will start the first transition from the vibrator off state, using
+ * the same frequency of the first control point. To provide a different initial vibration
+ * frequency, use {@link #startWaveformEnvelope(float)}.
*
* <p>Note: To check whether waveform envelope effects are supported, use
* {@link Vibrator#areEnvelopeEffectsSupported()}.
@@ -1754,6 +1756,32 @@ public abstract class VibrationEffect implements Parcelable {
}
/**
+ * Start building a waveform vibration with an initial frequency.
+ *
+ * <p>The waveform envelope builder offers more flexibility for creating waveform effects,
+ * allowing control over vibration amplitude and frequency via smooth transitions between
+ * values.
+ *
+ * <p>This is the same as {@link #startWaveformEnvelope()}, but the waveform will start
+ * vibrating at given frequency, in hertz, while it transitions to the new amplitude and
+ * frequency of the first control point.
+ *
+ * <p>Note: To check whether waveform envelope effects are supported, use
+ * {@link Vibrator#areEnvelopeEffectsSupported()}.
+ *
+ * @param initialFrequencyHz The starting frequency of the vibration, in hertz. Must be greater
+ * than zero.
+ *
+ * @see VibrationEffect.WaveformEnvelopeBuilder
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @NonNull
+ public static VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(
+ @FloatRange(from = 0) float initialFrequencyHz) {
+ return new WaveformEnvelopeBuilder(initialFrequencyHz);
+ }
+
+ /**
* A builder for waveform effects described by its envelope.
*
* <p>Waveform effect envelopes are defined by one or more control points describing a target
@@ -1810,6 +1838,10 @@ public abstract class VibrationEffect implements Parcelable {
private WaveformEnvelopeBuilder() {}
+ private WaveformEnvelopeBuilder(float initialFrequency) {
+ mLastFrequencyHz = initialFrequency;
+ }
+
/**
* Adds a new control point to the end of this waveform envelope.
*
@@ -1841,7 +1873,7 @@ public abstract class VibrationEffect implements Parcelable {
@FloatRange(from = 0, to = 1) float amplitude,
@FloatRange(from = 0) float frequencyHz, int timeMillis) {
- if (mSegments.isEmpty()) {
+ if (mLastFrequencyHz == 0) {
mLastFrequencyHz = frequencyHz;
}
diff --git a/core/java/android/os/vibrator/PwlePoint.java b/core/java/android/os/vibrator/PwlePoint.java
new file mode 100644
index 000000000000..ea3ae6c4649d
--- /dev/null
+++ b/core/java/android/os/vibrator/PwlePoint.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.os.vibrator;
+
+import java.util.Objects;
+
+/**
+ * A {@link PwlePoint} represents a single point in an envelope vibration effect. Defined by its
+ * amplitude, frequency and time to transition to this point from the previous one in the envelope.
+ *
+ * @hide
+ */
+public final class PwlePoint {
+ private final float mAmplitude;
+ private final float mFrequencyHz;
+ private final int mTimeMillis;
+
+ /** @hide */
+ public PwlePoint(float amplitude, float frequencyHz, int timeMillis) {
+ mAmplitude = amplitude;
+ mFrequencyHz = frequencyHz;
+ mTimeMillis = timeMillis;
+ }
+
+ public float getAmplitude() {
+ return mAmplitude;
+ }
+
+ public float getFrequencyHz() {
+ return mFrequencyHz;
+ }
+
+ public int getTimeMillis() {
+ return mTimeMillis;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PwlePoint)) {
+ return false;
+ }
+ PwlePoint other = (PwlePoint) obj;
+ return Float.compare(mAmplitude, other.mAmplitude) == 0
+ && Float.compare(mFrequencyHz, other.mFrequencyHz) == 0
+ && mTimeMillis == other.mTimeMillis;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAmplitude, mFrequencyHz, mTimeMillis);
+ }
+}
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index beb69858a85c..1cd1190c4080 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -425,6 +425,15 @@ public class VibrationEffectTest {
.build();
assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+ effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 60)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+ .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
+ .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40)
+ .build();
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
}
@Test
@@ -643,6 +652,14 @@ public class VibrationEffectTest {
.build()
.validate();
+ VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+ .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
+ .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40)
+ .build()
+ .validate();
+
VibrationEffect.createRepeatingEffect(
/*preamble=*/ VibrationEffect.startWaveformEnvelope()
.addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f,
@@ -693,6 +710,34 @@ public class VibrationEffectTest {
/*timeMillis=*/ 0)
.build()
.validate());
+
+ assertThrows(IllegalStateException.class,
+ () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+ .build().validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+ .addControlPoint(/*amplitude=*/ -1.0f, /*frequencyHz=*/ 60f,
+ /*timeMillis=*/ 20)
+ .build()
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+ .addControlPoint(/*amplitude=*/ 1.1f, /*frequencyHz=*/ 60f,
+ /*timeMillis=*/ 20)
+ .build()
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+ .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 0f,
+ /*timeMillis=*/ 20)
+ .build()
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+ .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 100f,
+ /*timeMillis=*/ 0)
+ .build()
+ .validate());
}
@Test
@@ -1331,6 +1376,11 @@ public class VibrationEffectTest {
.addTransition(Duration.ofMillis(500), targetAmplitude(0))
.build()
.isHapticFeedbackCandidate());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testIsHapticFeedbackCandidate_longEnvelopeEffects_notCandidates() {
assertFalse(VibrationEffect.startWaveformEnvelope()
.addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200)
.addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
@@ -1338,6 +1388,13 @@ public class VibrationEffectTest {
.addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
.build()
.isHapticFeedbackCandidate());
+ assertFalse(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 40)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200)
+ .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
+ .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 800)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
+ .build()
+ .isHapticFeedbackCandidate());
}
@Test
@@ -1351,12 +1408,23 @@ public class VibrationEffectTest {
.addTransition(Duration.ofMillis(300), targetAmplitude(0))
.build()
.isHapticFeedbackCandidate());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testIsHapticFeedbackCandidate_shortEnvelopeEffects_areCandidates() {
assertTrue(VibrationEffect.startWaveformEnvelope()
.addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
.addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
.addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100)
.build()
.isHapticFeedbackCandidate());
+ assertTrue(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+ .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
+ .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100)
+ .build()
+ .isHapticFeedbackCandidate());
}
@Test
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
index d0d6071e9dcb..32a322725692 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.vibrator.Flags;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.PwleSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
@@ -57,7 +58,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep {
// Load the next PwleSegments to create a single composePwleV2 call to the vibrator,
// limited to the vibrator's maximum envelope effect size.
int limit = controller.getVibratorInfo().getMaxEnvelopeEffectSize();
- List<PwleSegment> pwles = unrollPwleSegments(effect, segmentIndex, limit);
+ List<PwlePoint> pwles = unrollPwleSegments(effect, segmentIndex, limit);
if (pwles.isEmpty()) {
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposeEnvelopeStep: "
@@ -70,7 +71,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep {
Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
+ controller.getVibratorInfo().getId());
}
- PwleSegment[] pwlesArray = pwles.toArray(new PwleSegment[pwles.size()]);
+ PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]);
long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
handleVibratorOnResult(vibratorOnResult);
getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
@@ -82,9 +83,9 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep {
}
}
- private List<PwleSegment> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex,
+ private List<PwlePoint> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex,
int limit) {
- List<PwleSegment> segments = new ArrayList<>(limit);
+ List<PwlePoint> pwlePoints = new ArrayList<>(limit);
float bestBreakAmplitude = 1;
int bestBreakPosition = limit; // Exclusive index.
@@ -93,7 +94,7 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep {
// Loop once after reaching the limit to see if breaking it will really be necessary, then
// apply the best break position found, otherwise return the full list as it fits the limit.
- for (int i = startIndex; segments.size() <= limit; i++) {
+ for (int i = startIndex; pwlePoints.size() < limit; i++) {
if (i == segmentCount) {
if (repeatIndex >= 0) {
i = repeatIndex;
@@ -104,12 +105,20 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep {
}
VibrationEffectSegment segment = effect.getSegments().get(i);
if (segment instanceof PwleSegment pwleSegment) {
- segments.add(pwleSegment);
+ if (pwlePoints.isEmpty()) {
+ // The initial state is defined by the starting amplitude and frequency of the
+ // first PwleSegment. The time parameter is set to zero to indicate this is
+ // the initial condition without any ramp up time.
+ pwlePoints.add(new PwlePoint(pwleSegment.getStartAmplitude(),
+ pwleSegment.getStartFrequencyHz(), /*timeMillis=*/ 0));
+ }
+ pwlePoints.add(new PwlePoint(pwleSegment.getEndAmplitude(),
+ pwleSegment.getEndFrequencyHz(), (int) pwleSegment.getDuration()));
- if (isBetterBreakPosition(segments, bestBreakAmplitude, limit)) {
+ if (isBetterBreakPosition(pwlePoints, bestBreakAmplitude, limit)) {
// Mark this position as the best one so far to break a long waveform.
bestBreakAmplitude = pwleSegment.getEndAmplitude();
- bestBreakPosition = segments.size(); // Break after this pwle ends.
+ bestBreakPosition = pwlePoints.size(); // Break after this pwle ends.
}
} else {
// First non-pwle segment, stop collecting pwles.
@@ -117,21 +126,21 @@ final class ComposePwleV2VibratorStep extends AbstractComposedVibratorStep {
}
}
- return segments.size() > limit
+ return pwlePoints.size() > limit
// Remove excessive segments, using the best breaking position recorded.
- ? segments.subList(0, bestBreakPosition)
+ ? pwlePoints.subList(0, bestBreakPosition)
// Return all collected pwle segments.
- : segments;
+ : pwlePoints;
}
/**
* Returns true if the current segment list represents a better break position for a PWLE,
* given the current amplitude being used for breaking it at a smaller size and the size limit.
*/
- private boolean isBetterBreakPosition(List<PwleSegment> segments,
+ private boolean isBetterBreakPosition(List<PwlePoint> segments,
float currentBestBreakAmplitude, int limit) {
- PwleSegment lastSegment = segments.get(segments.size() - 1);
- float breakAmplitudeCandidate = lastSegment.getEndAmplitude();
+ PwlePoint lastSegment = segments.get(segments.size() - 1);
+ float breakAmplitudeCandidate = lastSegment.getAmplitude();
int breakPositionCandidate = segments.size();
if (breakPositionCandidate > limit) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index bc4dbe75b37e..de423f061b2b 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -22,7 +22,7 @@ import android.os.CombinedVibration;
import android.os.SystemClock;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -294,18 +294,22 @@ final class VibrationStats {
}
/** Report a call to vibrator method to trigger a vibration as a PWLE. */
- void reportComposePwle(long halResult, PwleSegment[] segments) {
+ void reportComposePwle(long halResult, PwlePoint[] pwlePoints) {
mVibratorComposePwleCount++;
- mVibrationPwleTotalSize += segments.length;
+ mVibrationPwleTotalSize += pwlePoints.length;
if (halResult > 0) {
// If HAL result is positive then it represents the actual duration of the vibration.
// Remove the zero-amplitude segments to update the total time the vibrator was ON.
- for (PwleSegment ramp : segments) {
- if ((ramp.getStartAmplitude() == 0) && (ramp.getEndAmplitude() == 0)) {
- halResult -= ramp.getDuration();
+ for (int i = 0; i < pwlePoints.length - 1; i++) {
+ PwlePoint current = pwlePoints[i];
+ PwlePoint next = pwlePoints[i + 1];
+
+ if (current.getAmplitude() == 0 && next.getAmplitude() == 0) {
+ halResult -= next.getTimeMillis();
}
}
+
if (halResult > 0) {
mVibratorOnTotalDurationMillis += (int) halResult;
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index f78bff8e229d..acb31ceb4027 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -30,7 +30,7 @@ import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -415,21 +415,21 @@ final class VibratorController {
}
/**
- * Plays a composition of pwle v2 primitives, using {@code vibrationId} for completion callback
+ * Plays a composition of pwle v2 points, using {@code vibrationId} for completion callback
* to {@link OnVibrationCompleteListener}.
*
* <p>This will affect the state of {@link #isVibrating()}.
*
* @return The duration of the effect playing, or 0 if unsupported.
*/
- public long on(PwleSegment[] primitives, long vibrationId) {
+ public long on(PwlePoint[] pwlePoints, long vibrationId) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE v2)");
try {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) {
return 0;
}
synchronized (mLock) {
- long duration = mNativeWrapper.composePwleV2(primitives, vibrationId);
+ long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -562,7 +562,7 @@ final class VibratorController {
private static native long performPwleEffect(long nativePtr, RampSegment[] effect,
int braking, long vibrationId);
- private static native long performPwleV2Effect(long nativePtr, PwleSegment[] effect,
+ private static native long performPwleV2Effect(long nativePtr, PwlePoint[] effect,
long vibrationId);
private static native void setExternalControl(long nativePtr, boolean enabled);
@@ -631,9 +631,9 @@ final class VibratorController {
return performPwleEffect(mNativePtr, primitives, braking, vibrationId);
}
- /** Turns vibrator on to perform PWLE effect composed of given primitives. */
- public long composePwleV2(PwleSegment[] primitives, long vibrationId) {
- return performPwleV2Effect(mNativePtr, primitives, vibrationId);
+ /** Turns vibrator on to perform PWLE effect composed of given points. */
+ public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
+ return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId);
}
/** Enabled the device vibrator to be controlled by another service. */
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 59dbf28b3d1c..0ecc0a8a9524 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -74,12 +74,10 @@ static struct {
jfieldID duration;
} sRampClassInfo;
static struct {
- jfieldID startAmplitude;
- jfieldID endAmplitude;
- jfieldID startFrequencyHz;
- jfieldID endFrequencyHz;
- jfieldID duration;
-} sPwleClassInfo;
+ jfieldID amplitude;
+ jfieldID frequencyHz;
+ jfieldID timeMillis;
+} sPwlePointClassInfo;
static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) ==
static_cast<uint8_t>(Aidl::EffectStrength::LIGHT));
@@ -191,10 +189,11 @@ static Aidl::ActivePwle activePwleFromJavaPrimitive(JNIEnv* env, jobject ramp) {
static Aidl::PwleV2Primitive pwleV2PrimitiveFromJavaPrimitive(JNIEnv* env, jobject pwleObj) {
Aidl::PwleV2Primitive pwle;
- pwle.amplitude = static_cast<float>(env->GetFloatField(pwleObj, sPwleClassInfo.endAmplitude));
+ pwle.amplitude = static_cast<float>(env->GetFloatField(pwleObj, sPwlePointClassInfo.amplitude));
pwle.frequencyHz =
- static_cast<float>(env->GetFloatField(pwleObj, sPwleClassInfo.endFrequencyHz));
- pwle.timeMillis = static_cast<int32_t>(env->GetIntField(pwleObj, sPwleClassInfo.duration));
+ static_cast<float>(env->GetFloatField(pwleObj, sPwlePointClassInfo.frequencyHz));
+ pwle.timeMillis =
+ static_cast<int32_t>(env->GetIntField(pwleObj, sPwlePointClassInfo.timeMillis));
return pwle;
}
@@ -620,7 +619,7 @@ static const JNINativeMethod method_table[] = {
(void*)vibratorPerformComposedEffect},
{"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
(void*)vibratorPerformPwleEffect},
- {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwleSegment;J)J",
+ {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;J)J",
(void*)vibratorPerformPwleV2Effect},
{"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
{"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
@@ -647,12 +646,10 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
sRampClassInfo.endFrequencyHz = GetFieldIDOrDie(env, rampClass, "mEndFrequencyHz", "F");
sRampClassInfo.duration = GetFieldIDOrDie(env, rampClass, "mDuration", "I");
- jclass pwleClass = FindClassOrDie(env, "android/os/vibrator/PwleSegment");
- sPwleClassInfo.startAmplitude = GetFieldIDOrDie(env, pwleClass, "mStartAmplitude", "F");
- sPwleClassInfo.endAmplitude = GetFieldIDOrDie(env, pwleClass, "mEndAmplitude", "F");
- sPwleClassInfo.startFrequencyHz = GetFieldIDOrDie(env, pwleClass, "mStartFrequencyHz", "F");
- sPwleClassInfo.endFrequencyHz = GetFieldIDOrDie(env, pwleClass, "mEndFrequencyHz", "F");
- sPwleClassInfo.duration = GetFieldIDOrDie(env, pwleClass, "mDuration", "I");
+ jclass pwlePointClass = FindClassOrDie(env, "android/os/vibrator/PwlePoint");
+ sPwlePointClassInfo.amplitude = GetFieldIDOrDie(env, pwlePointClass, "mAmplitude", "F");
+ sPwlePointClassInfo.frequencyHz = GetFieldIDOrDie(env, pwlePointClass, "mFrequencyHz", "F");
+ sPwlePointClassInfo.timeMillis = GetFieldIDOrDie(env, pwlePointClass, "mTimeMillis", "I");
jclass frequencyProfileLegacyClass =
FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfileLegacy");
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 8aa8a84206e2..093359042a3e 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -59,7 +59,7 @@ import android.os.test.TestLooper;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
@@ -888,7 +888,7 @@ public class VibrationThreadTest {
fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
VibrationEffect effect = VibrationEffect.startWaveformEnvelope()
- .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+ .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
.addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30)
.addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20)
.addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
@@ -902,20 +902,47 @@ public class VibrationThreadTest {
verifyCallbacksTriggered(vibration, Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
- expectedPwle(/* startAmplitude= */ 0.0f, /* endAmplitude= */ 0.0f,
- /* startFrequencyHz= */ 60f, /* endFrequencyHz= */ 60f,
- /* duration= */ 20),
- expectedPwle(/* startAmplitude= */ 0.0f, /* endAmplitude= */ 0.3f,
- /* startFrequencyHz= */ 60f, /* endFrequencyHz= */ 100f,
- /* duration= */ 30),
- expectedPwle(/* startAmplitude= */ 0.3f, /* endAmplitude= */ 0.4f,
- /* startFrequencyHz= */ 100f, /* endFrequencyHz= */ 120f,
- /* duration= */ 20),
- expectedPwle(/* startAmplitude= */ 0.4f, /* endAmplitude= */ 0.0f,
- /* startFrequencyHz= */ 120f, /* endFrequencyHz= */ 120f,
- /* duration= */ 30)
- ),
- fakeVibrator.getEffectSegments(vibration.id));
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 0),
+ expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
+ expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
+ expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+ ), fakeVibrator.getEffectPwlePoints(vibration.id));
+
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void vibrate_singleVibratorPwle_withInitialFrequency_runsComposePwleV2() {
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+ fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+ fakeVibrator.setResonantFrequency(150);
+ fakeVibrator.setFrequenciesHz(new float[]{30f, 50f, 100f, 120f, 150f});
+ fakeVibrator.setOutputAccelerationsGs(new float[]{0.3f, 0.5f, 1.0f, 0.8f, 0.6f});
+ fakeVibrator.setMaxEnvelopeEffectSize(10);
+ fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
+
+ VibrationEffect effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+ .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+ .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30)
+ .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20)
+ .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+ .build();
+ HalVibration vibration = startThreadAndDispatcher(effect);
+ waitForCompletion();
+
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
+ verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.FINISHED);
+ assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+ assertEquals(Arrays.asList(
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 30f, /*timeMillis=*/ 0),
+ expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
+ expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
+ expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
+ expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+ ), fakeVibrator.getEffectPwlePoints(vibration.id));
}
@@ -2013,10 +2040,8 @@ public class VibrationThreadTest {
duration);
}
- private VibrationEffectSegment expectedPwle(float startAmplitude, float endAmplitude,
- float startFrequencyHz, float endFrequencyHz, int duration) {
- return new PwleSegment(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz,
- duration);
+ private PwlePoint expectedPwle(float amplitude, float frequencyHz, int timeMillis) {
+ return new PwlePoint(amplitude, frequencyHz, timeMillis);
}
private List<Float> expectedAmplitudes(int... amplitudes) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
index bc8db3be5f27..0978f48491cc 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -44,7 +44,7 @@ import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import androidx.test.InstrumentationRegistry;
@@ -274,9 +274,9 @@ public class VibratorControllerTest {
when(mNativeWrapperMock.composePwleV2(any(), anyLong())).thenReturn(15L);
VibratorController controller = createController();
- PwleSegment[] primitives = new PwleSegment[]{
- new PwleSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
- /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 10)
+ PwlePoint[] primitives = new PwlePoint[]{
+ new PwlePoint(/*amplitude=*/ 0, /*frequencyHz=*/ 100, /*timeMillis=*/ 0),
+ new PwlePoint(/*amplitude=*/ 1, /*frequencyHz=*/ 200, /*timeMillis=*/ 10)
};
assertEquals(15L, controller.on(primitives, 12));
assertTrue(controller.isVibrating());
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 2c3e9b29d11f..4dc59c20c431 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -26,7 +26,7 @@ import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
@@ -50,6 +50,7 @@ public final class FakeVibratorControllerProvider {
private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
private final Map<Long, List<VibrationEffect.VendorEffect>> mVendorEffects = new TreeMap<>();
+ private final Map<Long, List<PwlePoint>> mEffectPwlePoints = new TreeMap<>();
private final Map<Long, List<Integer>> mBraking = new HashMap<>();
private final List<Float> mAmplitudes = new ArrayList<>();
private final List<Boolean> mExternalControlStates = new ArrayList<>();
@@ -91,6 +92,10 @@ public final class FakeVibratorControllerProvider {
mVendorEffects.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(vendorEffect);
}
+ void recordEffectPwlePoint(long vibrationId, PwlePoint pwlePoint) {
+ mEffectPwlePoints.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(pwlePoint);
+ }
+
void recordBraking(long vibrationId, int braking) {
mBraking.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(braking);
}
@@ -195,11 +200,11 @@ public final class FakeVibratorControllerProvider {
}
@Override
- public long composePwleV2(PwleSegment[] primitives, long vibrationId) {
+ public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
long duration = 0;
- for (PwleSegment primitive: primitives) {
- duration += primitive.getDuration();
- recordEffectSegment(vibrationId, primitive);
+ for (PwlePoint pwlePoint: pwlePoints) {
+ duration += pwlePoint.getTimeMillis();
+ recordEffectPwlePoint(vibrationId, pwlePoint);
}
applyLatency(mOnLatency);
scheduleListener(duration, vibrationId);
@@ -482,6 +487,28 @@ public final class FakeVibratorControllerProvider {
return result;
}
+ /** Return list of {@link PwlePoint} played by this controller, in order. */
+ public List<PwlePoint> getEffectPwlePoints(long vibrationId) {
+ if (mEffectPwlePoints.containsKey(vibrationId)) {
+ return new ArrayList<>(mEffectPwlePoints.get(vibrationId));
+ } else {
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * Returns a list of all vibrations' {@link PwlePoint}s, for external-use where vibration
+ * IDs aren't exposed.
+ */
+ public List<PwlePoint> getAllEffectPwlePoints() {
+ // Returns segments in order of vibrationId, which increases over time. TreeMap gives order.
+ ArrayList<PwlePoint> result = new ArrayList<>();
+ for (List<PwlePoint> subList : mEffectPwlePoints.values()) {
+ result.addAll(subList);
+ }
+ return result;
+ }
+
/** Return list of states set for external control to the fake vibrator hardware. */
public List<Boolean> getExternalControlStates() {
return mExternalControlStates;