diff options
| author | 2024-11-01 16:12:44 +0000 | |
|---|---|---|
| committer | 2024-11-01 16:12:44 +0000 | |
| commit | 3c9c6d3ef740a03650b8cb3bb2bc7b6fe93035d9 (patch) | |
| tree | e2ba435eabc5eecfc0340be4a65412e30c4c8b04 | |
| parent | ce7e02273bdd074cdaba79f97a46fdd9263c958b (diff) | |
| parent | b563aba2740e79b89821e052061da6bc5732351c (diff) | |
Merge "Add initial frequency to envelope effects" into main
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; |