Add new vibrator frequency profile for PWLE v2
This new vibrator frequency profile provides developers with richer information about the vibrators capabilities, including:
1. The minimum and maximum frequencies supported by the vibrator.
2. The maximum output acceleration the vibrator can achieve.
3. Retrieve frequency range for a specified minimum output acceleration
This profile will only be available if the device supports frequency control.
Bug: 347034419
Flag: android.os.vibrator.normalized_pwle_effects
Test: atest FrameworksVibratorCoreTests
Change-Id: I9a16b9b4932d95e8eb8c89bfa43f94920ed9e1ee
diff --git a/core/api/current.txt b/core/api/current.txt
index 44b3c62..ca45d1b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -34334,6 +34334,7 @@
method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public boolean areEnvelopeEffectsSupported();
method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @Nullable public android.os.vibrator.VibratorFrequencyProfile getFrequencyProfile();
method public int getId();
method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectControlPointDurationMillis();
method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectDurationMillis();
@@ -34687,6 +34688,19 @@
}
+package android.os.vibrator {
+
+ @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public final class VibratorFrequencyProfile {
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.util.SparseArray<java.lang.Float> getFrequenciesOutputAcceleration();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @Nullable public android.util.Range<java.lang.Float> getFrequencyRange(float);
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public float getMaxFrequencyHz();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public float getMaxOutputAccelerationGs();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public float getMinFrequencyHz();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public float getOutputAccelerationGs(float);
+ }
+
+}
+
package android.preference {
@Deprecated public class CheckBoxPreference extends android.preference.TwoStatePreference {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 7327630..c4c4580 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -34,6 +34,7 @@
import android.media.AudioAttributes;
import android.os.vibrator.Flags;
import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibratorFrequencyProfile;
import android.os.vibrator.VibratorFrequencyProfileLegacy;
import android.util.Log;
import android.view.HapticFeedbackConstants;
@@ -303,6 +304,28 @@
}
/**
+ * Gets the profile that describes the vibrator output across the supported frequency range.
+ *
+ * <p>The profile describes the output acceleration that the device can reach when it
+ * vibrates at different frequencies.
+ *
+ * @return The frequency profile for this vibrator, or null if the vibrator does not have
+ * frequency control. If this vibrator is a composite of multiple physical devices then this
+ * will return a profile supported in all devices, or null if the intersection is empty or not
+ * available.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @Nullable
+ public VibratorFrequencyProfile getFrequencyProfile() {
+ VibratorInfo.FrequencyProfile frequencyProfile = getInfo().getFrequencyProfile();
+ if (frequencyProfile.isEmpty()) {
+ return null;
+ }
+
+ return new VibratorFrequencyProfile(frequencyProfile);
+ }
+
+ /**
* Return the maximum amplitude the vibrator can play using the audio haptic channels.
*
* <p>This is a positive value, or {@link Float#NaN NaN} if it's unknown. If this returns a
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index f7fff39..9419032 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -20,8 +20,10 @@
import android.annotation.Nullable;
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
+import android.os.vibrator.Flags;
import android.util.IndentingPrintWriter;
import android.util.MathUtils;
+import android.util.Pair;
import android.util.Range;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -30,8 +32,11 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.TreeMap;
/**
* A VibratorInfo describes the capabilities of a {@link Vibrator}.
@@ -60,6 +65,7 @@
private final int mPwleSizeMax;
private final float mQFactor;
private final FrequencyProfileLegacy mFrequencyProfileLegacy;
+ private final FrequencyProfile mFrequencyProfile;
private final int mMaxEnvelopeEffectSize;
private final int mMinEnvelopeEffectControlPointDurationMillis;
private final int mMaxEnvelopeEffectControlPointDurationMillis;
@@ -76,6 +82,7 @@
mPwleSizeMax = in.readInt();
mQFactor = in.readFloat();
mFrequencyProfileLegacy = FrequencyProfileLegacy.CREATOR.createFromParcel(in);
+ mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in);
mMaxEnvelopeEffectSize = in.readInt();
mMinEnvelopeEffectControlPointDurationMillis = in.readInt();
mMaxEnvelopeEffectControlPointDurationMillis = in.readInt();
@@ -87,7 +94,7 @@
baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax,
baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax,
baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfileLegacy,
- baseVibratorInfo.mMaxEnvelopeEffectSize,
+ baseVibratorInfo.mFrequencyProfile, baseVibratorInfo.mMaxEnvelopeEffectSize,
baseVibratorInfo.mMinEnvelopeEffectControlPointDurationMillis,
baseVibratorInfo.mMaxEnvelopeEffectControlPointDurationMillis);
}
@@ -114,6 +121,17 @@
* @param qFactor The vibrator quality factor.
* @param frequencyProfileLegacy The description of the vibrator supported frequencies and max
* amplitude mappings.
+ * @param frequencyProfile The description of the vibrator supported frequencies and
+ * output acceleration mappings.
+ * @param maxEnvelopeEffectSize The maximum number of control points supported for an
+ * envelope effect.
+ * @param minEnvelopeEffectControlPointDurationMillis The minimum duration supported
+ * between two control points within an
+ * envelope effect.
+ * @param maxEnvelopeEffectControlPointDurationMillis The maximum duration supported
+ * between two control points within an
+ * envelope effect.
+ *
* @hide
*/
public VibratorInfo(int id, long capabilities, @Nullable SparseBooleanArray supportedEffects,
@@ -121,10 +139,12 @@
@NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
float qFactor, @NonNull FrequencyProfileLegacy frequencyProfileLegacy,
- int maxEnvelopeEffectSize, int minEnvelopeEffectControlPointDurationMillis,
+ @NonNull FrequencyProfile frequencyProfile, int maxEnvelopeEffectSize,
+ int minEnvelopeEffectControlPointDurationMillis,
int maxEnvelopeEffectControlPointDurationMillis) {
Preconditions.checkNotNull(supportedPrimitives);
Preconditions.checkNotNull(frequencyProfileLegacy);
+ Preconditions.checkNotNull(frequencyProfile);
mId = id;
mCapabilities = capabilities;
mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
@@ -136,6 +156,7 @@
mPwleSizeMax = pwleSizeMax;
mQFactor = qFactor;
mFrequencyProfileLegacy = frequencyProfileLegacy;
+ mFrequencyProfile = frequencyProfile;
mMaxEnvelopeEffectSize = maxEnvelopeEffectSize;
mMinEnvelopeEffectControlPointDurationMillis =
minEnvelopeEffectControlPointDurationMillis;
@@ -156,6 +177,7 @@
dest.writeInt(mPwleSizeMax);
dest.writeFloat(mQFactor);
mFrequencyProfileLegacy.writeToParcel(dest, flags);
+ mFrequencyProfile.writeToParcel(dest, flags);
dest.writeInt(mMaxEnvelopeEffectSize);
dest.writeInt(mMinEnvelopeEffectControlPointDurationMillis);
dest.writeInt(mMaxEnvelopeEffectControlPointDurationMillis);
@@ -206,6 +228,7 @@
&& Objects.equals(mSupportedBraking, that.mSupportedBraking)
&& Objects.equals(mQFactor, that.mQFactor)
&& Objects.equals(mFrequencyProfileLegacy, that.mFrequencyProfileLegacy)
+ && Objects.equals(mFrequencyProfile, that.mFrequencyProfile)
&& mMaxEnvelopeEffectSize == that.mMaxEnvelopeEffectSize
&& mMinEnvelopeEffectControlPointDurationMillis
== that.mMinEnvelopeEffectControlPointDurationMillis
@@ -216,7 +239,7 @@
@Override
public int hashCode() {
int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
- mQFactor, mFrequencyProfileLegacy);
+ mQFactor, mFrequencyProfileLegacy, mFrequencyProfile);
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
@@ -239,6 +262,7 @@
+ ", mPwleSizeMax=" + mPwleSizeMax
+ ", mQFactor=" + mQFactor
+ ", mFrequencyProfileLegacy=" + mFrequencyProfileLegacy
+ + ", mFrequencyProfile=" + mFrequencyProfile
+ ", mMaxEnvelopeEffectSize=" + mMaxEnvelopeEffectSize
+ ", mMinEnvelopeEffectControlPointDurationMillis="
+ mMinEnvelopeEffectControlPointDurationMillis
@@ -263,6 +287,7 @@
pw.println("pwleSizeMax = " + mPwleSizeMax);
pw.println("q-factor = " + mQFactor);
pw.println("frequencyProfileLegacy = " + mFrequencyProfileLegacy);
+ pw.println("frequencyProfile = " + mFrequencyProfile);
pw.println("mMaxEnvelopeEffectSize = " + mMaxEnvelopeEffectSize);
pw.println("mMinEnvelopeEffectControlPointDurationMillis = "
+ mMinEnvelopeEffectControlPointDurationMillis);
@@ -517,6 +542,9 @@
* this vibrator is a composite of multiple physical devices.
*/
public float getResonantFrequencyHz() {
+ if (Flags.normalizedPwleEffects()) {
+ return mFrequencyProfile.mResonantFrequencyHz;
+ }
return mFrequencyProfileLegacy.mResonantFrequencyHz;
}
@@ -541,6 +569,17 @@
return mFrequencyProfileLegacy;
}
+ /**
+ * Gets the profile of supported frequencies, including the measurements of maximum
+ * output acceleration for supported vibration frequencies.
+ *
+ * <p>If the devices does not have frequency control then the profile should be empty.
+ */
+ @NonNull
+ public FrequencyProfile getFrequencyProfile() {
+ return mFrequencyProfile;
+ }
+
/** Returns a single int representing all the capabilities of the vibrator. */
public long getCapabilities() {
return mCapabilities;
@@ -623,6 +662,304 @@
}
/**
+ * Describes the maximum output acceleration that can be achieved for each supported
+ * frequency in a specific vibrator.
+ *
+ * @hide
+ */
+ public static final class FrequencyProfile implements Parcelable {
+
+ private final float[] mFrequenciesHz;
+ private final float[] mOutputAccelerationsGs;
+ private final float mResonantFrequencyHz;
+ private final float mMaxOutputAccelerationGs;
+ private final float mMinFrequencyHz;
+ private final float mMaxFrequencyHz;
+
+ public FrequencyProfile(Parcel in) {
+ this(in.readFloat(), in.createFloatArray(), in.createFloatArray());
+ }
+
+ /**
+ * Default constructor.
+ *
+ * @param resonantFrequencyHz The vibrator resonant frequency, in hertz.
+ * @param frequenciesHz The supported vibration frequencies, in hertz.
+ * @param outputAccelerationsGs The maximum achievable output acceleration (in Gs) the
+ * device can reach at the supported frequencies.
+ */
+ public FrequencyProfile(float resonantFrequencyHz, float[] frequenciesHz,
+ float[] outputAccelerationsGs) {
+
+ mResonantFrequencyHz = resonantFrequencyHz;
+
+ boolean isValid = !Float.isNaN(resonantFrequencyHz)
+ && (resonantFrequencyHz > 0)
+ && (frequenciesHz != null && outputAccelerationsGs != null)
+ && (frequenciesHz.length == outputAccelerationsGs.length)
+ && (frequenciesHz.length > 0);
+
+ if (!isValid) {
+ mFrequenciesHz = null;
+ mOutputAccelerationsGs = null;
+ mMinFrequencyHz = Float.NaN;
+ mMaxFrequencyHz = Float.NaN;
+ mMaxOutputAccelerationGs = Float.NaN;
+ return;
+ }
+
+ TreeMap<Float, Float> frequencyToOutputAccelerationMap = new TreeMap<>();
+
+ for (int i = 0; i < frequenciesHz.length; i++) {
+ frequencyToOutputAccelerationMap.putIfAbsent(frequenciesHz[i],
+ outputAccelerationsGs[i]);
+ }
+
+ float[] frequencies = new float[frequencyToOutputAccelerationMap.size()];
+ float[] accelerations = new float[frequencyToOutputAccelerationMap.size()];
+ float maxOutputAccelerationGs = 0;
+ int i = 0;
+ for (Map.Entry<Float, Float> entry : frequencyToOutputAccelerationMap.entrySet()) {
+ frequencies[i] = entry.getKey();
+ accelerations[i] = entry.getValue();
+ maxOutputAccelerationGs = Math.max(maxOutputAccelerationGs, entry.getValue());
+ i++;
+ }
+
+ mFrequenciesHz = frequencies;
+ mOutputAccelerationsGs = accelerations;
+ mMinFrequencyHz = mFrequenciesHz[0];
+ mMaxFrequencyHz = mFrequenciesHz[mFrequenciesHz.length - 1];
+ mMaxOutputAccelerationGs = maxOutputAccelerationGs;
+ }
+
+ /** Returns true if the supported frequency range is null. */
+ public boolean isEmpty() {
+ return mFrequenciesHz == null;
+ }
+
+ /**
+ * Returns a list of available frequencies.
+ */
+ @Nullable
+ public float[] getFrequenciesHz() {
+ return mFrequenciesHz;
+ }
+
+ /** Returns the list of available output accelerations */
+ @Nullable
+ public float[] getOutputAccelerationsGs() {
+ return mOutputAccelerationsGs;
+ }
+
+ /** Maximum output acceleration reachable in Gs when amplitude is 1.0f. */
+ public float getMaxOutputAccelerationGs() {
+ return mMaxOutputAccelerationGs;
+ }
+
+ /**
+ * Calculates the maximum output acceleration for a given frequency using linear
+ * interpolation.
+ *
+ * @param frequencyHz frequency, in hertz, for query.
+ * @return the maximum output acceleration for the given frequency.
+ */
+ public float getOutputAccelerationGs(float frequencyHz) {
+ if (mFrequenciesHz == null) {
+ return Float.NaN;
+ }
+
+ if (frequencyHz < mMinFrequencyHz || frequencyHz > mMaxFrequencyHz) {
+ // Outside supported frequency range, not able to vibrate at this frequency.
+ return 0;
+ }
+
+ int idx = Arrays.binarySearch(mFrequenciesHz, frequencyHz);
+ if (idx >= 0) {
+ return mOutputAccelerationsGs[idx];
+ }
+
+ // This indicates that the value was not found in the list. Adjust index of the
+ // insertion point to be at the lower bound.
+ idx = -idx - 2;
+
+ // Linearly interpolate the output acceleration based on the frequency.
+ return MathUtils.constrainedMap(
+ mOutputAccelerationsGs[idx], mOutputAccelerationsGs[idx + 1],
+ mFrequenciesHz[idx], mFrequenciesHz[idx + 1],
+ frequencyHz);
+ }
+
+ /** The minimum frequency supported, in hertz. */
+ public float getMinFrequencyHz() {
+ return mMinFrequencyHz;
+ }
+
+ /** The maximum frequency supported, in hertz. */
+ public float getMaxFrequencyHz() {
+ return mMaxFrequencyHz;
+ }
+
+ /**
+ * Returns the frequency range that supports the specified minimum output
+ * acceleration.
+ *
+ * @return The frequency range, or null if the specified acceleration
+ * is not achievable on the device.
+ */
+ @Nullable
+ public Range<Float> getFrequencyRangeHz(float minOutputAcceleration) {
+ if (mFrequenciesHz == null || mOutputAccelerationsGs == null
+ || minOutputAcceleration > mMaxOutputAccelerationGs) {
+ return null; // No frequency range available
+ }
+
+ if (minOutputAcceleration <= 0) {
+ return new Range<>(mMinFrequencyHz, mMaxFrequencyHz);
+ }
+
+ float minFrequency = Float.NaN;
+ float maxFrequency = Float.NaN;
+ int lowerFrequencyBoundIndex = 0;
+ // Find the lower frequency bound
+ for (int i = 0; i < mOutputAccelerationsGs.length; i++) {
+ if (mOutputAccelerationsGs[i] >= minOutputAcceleration) {
+ if (i == 0) {
+ minFrequency = mMinFrequencyHz;
+ } else {
+ minFrequency = MathUtils.constrainedMap(
+ mFrequenciesHz[i - 1], mFrequenciesHz[i],
+ mOutputAccelerationsGs[i - 1], mOutputAccelerationsGs[i],
+ minOutputAcceleration);
+ }
+ lowerFrequencyBoundIndex = i;
+ break; // Found the lower bound
+ }
+ }
+
+ if (Float.isNaN(minFrequency)) {
+ // Lower bound was not found
+ return null;
+ }
+
+ // Find the upper frequency bound
+ for (int i = lowerFrequencyBoundIndex; i < mOutputAccelerationsGs.length; i++) {
+ if (mOutputAccelerationsGs[i] <= minOutputAcceleration) {
+ maxFrequency = MathUtils.constrainedMap(
+ mFrequenciesHz[i - 1], mFrequenciesHz[i],
+ mOutputAccelerationsGs[i - 1], mOutputAccelerationsGs[i],
+ minOutputAcceleration);
+ break; // Found the upper bound
+ }
+ }
+
+ if (Float.isNaN(maxFrequency)) {
+ // If the upper bound was not found, the specified output acceleration is
+ // achievable at all remaining frequencies.
+ maxFrequency = mMaxFrequencyHz;
+ }
+
+ return new Range<>(minFrequency, maxFrequency);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mResonantFrequencyHz);
+ dest.writeFloatArray(mFrequenciesHz);
+ dest.writeFloatArray(mOutputAccelerationsGs);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FrequencyProfile that)) {
+ return false;
+ }
+ return Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
+ && Arrays.equals(mFrequenciesHz, that.mFrequenciesHz)
+ && Arrays.equals(mOutputAccelerationsGs, that.mOutputAccelerationsGs);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mResonantFrequencyHz, Arrays.hashCode(mFrequenciesHz),
+ Arrays.hashCode(mOutputAccelerationsGs));
+ }
+
+ @Override
+ public String toString() {
+ return "FrequencyProfile{"
+ + "mResonantFrequency=" + mResonantFrequencyHz
+ + ", mFrequenciesHz=" + Arrays.toString(mFrequenciesHz)
+ + ", mOutputAccelerationsGs=" + Arrays.toString(mOutputAccelerationsGs)
+ + ", mMinFrequencyHz=" + mMinFrequencyHz
+ + ", mMaxFrequencyHz=" + mMaxFrequencyHz
+ + ", mMaxOutputAccelerationGs=" + mMaxOutputAccelerationGs
+ + '}';
+ }
+
+ @NonNull
+ public static final Creator<FrequencyProfile> CREATOR =
+ new Creator<FrequencyProfile>() {
+ @Override
+ public FrequencyProfile createFromParcel(Parcel in) {
+ return new FrequencyProfile(in);
+ }
+
+ @Override
+ public FrequencyProfile[] newArray(int size) {
+ return new FrequencyProfile[size];
+ }
+ };
+
+ private static void deduplicateAndSortList(List<Pair<Float, Float>> list) {
+ if (list == null || list.size() < 2) {
+ return; // Nothing to dedupe
+ }
+
+ list.sort(Comparator.comparing(pair -> pair.first));
+
+ // Remove duplicates from the list
+ int writeIndex = 1;
+ for (int i = 1; i < list.size(); i++) {
+ Pair<Float, Float> currentPair = list.get(i);
+ Pair<Float, Float> previousPair = list.get(writeIndex - 1);
+
+ if (currentPair.first.compareTo(previousPair.first) != 0) {
+ list.set(writeIndex++, currentPair);
+ }
+ }
+ list.subList(writeIndex, list.size()).clear();
+ }
+
+ private static ArrayList<Pair<Float, Float>> extractFrequencyToOutputAccelerationData(
+ float[] frequencies, float[] outputAccelerations) {
+
+ if (frequencies == null || outputAccelerations == null
+ || frequencies.length == 0
+ || frequencies.length != outputAccelerations.length) {
+ return new ArrayList<>(); // Return empty list for invalid or mismatched data
+ }
+
+ ArrayList<Pair<Float, Float>> frequencyToOutputAccelerationList = new ArrayList<>(
+ frequencies.length);
+ for (int i = 0; i < frequencies.length; i++) {
+ frequencyToOutputAccelerationList.add(
+ new Pair<>(frequencies[i], outputAccelerations[i]));
+ }
+
+ return frequencyToOutputAccelerationList;
+ }
+ }
+
+ /**
* Describes the maximum relative output acceleration that can be achieved for each supported
* frequency in a specific vibrator.
*
@@ -834,6 +1171,8 @@
private float mQFactor = Float.NaN;
private FrequencyProfileLegacy mFrequencyProfileLegacy =
new FrequencyProfileLegacy(Float.NaN, Float.NaN, Float.NaN, null);
+ private FrequencyProfile mFrequencyProfile = new FrequencyProfile(Float.NaN, null,
+ null);
private int mMaxEnvelopeEffectSize;
private int mMinEnvelopeEffectControlPointDurationMillis;
private int mMaxEnvelopeEffectControlPointDurationMillis;
@@ -914,6 +1253,16 @@
}
/**
+ * Configure the vibrator frequency information like resonant frequency and frequency to
+ * output acceleration data.
+ */
+ @NonNull
+ public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) {
+ mFrequencyProfile = frequencyProfile;
+ return this;
+ }
+
+ /**
* Configure the maximum number of control points supported for envelope effects on this
* device.
*/
@@ -951,7 +1300,8 @@
return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax,
mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfileLegacy,
- mMaxEnvelopeEffectSize, mMinEnvelopeEffectControlPointDurationMillis,
+ mFrequencyProfile, mMaxEnvelopeEffectSize,
+ mMinEnvelopeEffectControlPointDurationMillis,
mMaxEnvelopeEffectControlPointDurationMillis);
}
diff --git a/core/java/android/os/vibrator/MultiVibratorInfo.java b/core/java/android/os/vibrator/MultiVibratorInfo.java
index 37dae56..1ba8d99 100644
--- a/core/java/android/os/vibrator/MultiVibratorInfo.java
+++ b/core/java/android/os/vibrator/MultiVibratorInfo.java
@@ -27,6 +27,9 @@
import android.util.SparseIntArray;
import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
import java.util.function.Function;
/**
@@ -44,13 +47,18 @@
private static final float EPSILON = 1e-5f;
public MultiVibratorInfo(int id, VibratorInfo[] vibrators) {
- this(id, vibrators, frequencyProfileIntersection(vibrators));
+ this(id, vibrators, frequencyProfileLegacyIntersection(vibrators),
+ frequencyProfileIntersection(vibrators));
}
private MultiVibratorInfo(
- int id, VibratorInfo[] vibrators, FrequencyProfileLegacy mergedProfile) {
+ int id, VibratorInfo[] vibrators,
+ VibratorInfo.FrequencyProfileLegacy mergedLegacyProfile,
+ FrequencyProfile mergedProfile) {
super(id,
- capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
+ capabilitiesIntersection(vibrators,
+ Flags.normalizedPwleEffects() ? mergedProfile.isEmpty()
+ : mergedLegacyProfile.isEmpty()),
supportedEffectsIntersection(vibrators),
supportedBrakingIntersection(vibrators),
supportedPrimitivesAndDurationsIntersection(vibrators),
@@ -59,6 +67,7 @@
integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
+ mergedLegacyProfile,
mergedProfile,
integerLimitIntersection(vibrators,
VibratorInfo::getMaxEnvelopeEffectSize),
@@ -209,7 +218,82 @@
}
@NonNull
- private static FrequencyProfileLegacy frequencyProfileIntersection(VibratorInfo[] infos) {
+ private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
+ if (infos == null || infos.length == 0) {
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ float resonantFreq = floatPropertyIntersection(infos, VibratorInfo::getResonantFrequencyHz);
+
+ if (Float.isNaN(resonantFreq)) {
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ float minFrequency = 0.0f;
+ float maxFrequency = Float.MAX_VALUE;
+ Set<Float> allFrequencies = new TreeSet<>(); // Using TreeSet for automatic sorting
+
+ for (VibratorInfo info : infos) {
+ float newMinFrequency = info.getFrequencyProfile().getMinFrequencyHz();
+ float newMaxFrequency = info.getFrequencyProfile().getMaxFrequencyHz();
+
+ if (Float.isNaN(newMinFrequency) || Float.isNaN(newMaxFrequency)) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ minFrequency = Math.max(minFrequency, newMinFrequency);
+ maxFrequency = Math.min(maxFrequency, newMaxFrequency);
+
+ if (info.getFrequencyProfile().getFrequenciesHz() == null) {
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ for (float frequency : info.getFrequencyProfile().getFrequenciesHz()) {
+ allFrequencies.add(frequency);
+ }
+ }
+
+ if (minFrequency > maxFrequency) {
+ // If the range and intersection are disjoint then the intersection is undefined
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ // Trim frequencies to the min/max range
+ Iterator<Float> iterator = allFrequencies.iterator();
+ while (iterator.hasNext()) {
+ float frequency = iterator.next();
+ if (frequency < minFrequency || frequency > maxFrequency) {
+ iterator.remove();
+ }
+ }
+
+ float[] frequencies = new float[allFrequencies.size()];
+ float[] accelerations = new float[allFrequencies.size()];
+ int idx = 0;
+
+ for (Float frequency : allFrequencies) {
+ float outputAcceleration = Float.MAX_VALUE;
+ for (VibratorInfo info : infos) {
+ // This will find the mapped value or interpolate it if needed.
+ outputAcceleration = Math.min(outputAcceleration,
+ info.getFrequencyProfile().getOutputAccelerationGs(frequency));
+ }
+ frequencies[idx] = frequency;
+ accelerations[idx] = outputAcceleration;
+ idx++;
+ }
+
+ return new FrequencyProfile(resonantFreq, frequencies, accelerations);
+ }
+
+ @NonNull
+ private static FrequencyProfileLegacy frequencyProfileLegacyIntersection(VibratorInfo[] infos) {
float freqResolution = floatPropertyIntersection(infos,
info -> info.getFrequencyProfileLegacy().getFrequencyResolutionHz());
float resonantFreq = floatPropertyIntersection(infos,
diff --git a/core/java/android/os/vibrator/VibratorFrequencyProfile.java b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
new file mode 100644
index 0000000..2b5f9bf
--- /dev/null
+++ b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
@@ -0,0 +1,150 @@
+/*
+ * 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 android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.VibratorInfo;
+import android.util.Range;
+import android.util.SparseArray;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Describes the output of a {@link android.os.Vibrator} for different vibration frequencies.
+ *
+ * <p>The profile contains the vibrator's frequency range (minimum/maximum) and maximum
+ * acceleration, enabling retrieval of supported acceleration levels for specific frequencies, if
+ * the device supports independent frequency control.
+ *
+ * <p>It also describes the max output acceleration (Gs), of a vibration at different supported
+ * frequencies (Hz).
+ *
+ * <p>Vibrators without independent frequency control do not have a frequency profile.
+ */
+@FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+public final class VibratorFrequencyProfile {
+
+ private final VibratorInfo.FrequencyProfile mFrequencyProfile;
+ private final SparseArray<Float> mFrequenciesOutputAcceleration;
+
+ /** @hide */
+ public VibratorFrequencyProfile(@NonNull VibratorInfo.FrequencyProfile frequencyProfile) {
+ Objects.requireNonNull(frequencyProfile);
+ Preconditions.checkArgument(!frequencyProfile.isEmpty(),
+ "Frequency profile must not be empty");
+ mFrequencyProfile = frequencyProfile;
+ mFrequenciesOutputAcceleration = generateFrequencyToAccelerationMap(
+ frequencyProfile.getFrequenciesHz(), frequencyProfile.getOutputAccelerationsGs());
+ }
+
+ /**
+ * Returns a {@link SparseArray} representing the vibrator's output acceleration capabilities
+ * across different frequencies. This map defines the maximum acceleration
+ * the vibrator can achieve at each supported frequency.
+ * <p>The map's keys are frequencies in Hz, and the corresponding values
+ * are the maximum achievable output accelerations in Gs.
+ *
+ * @return A map of frequencies (Hz) to maximum accelerations (Gs).
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @NonNull
+ public SparseArray<Float> getFrequenciesOutputAcceleration() {
+ return mFrequenciesOutputAcceleration;
+ }
+
+ /**
+ * Returns the maximum output acceleration (in Gs) supported by the vibrator.
+ * This value represents the highest acceleration the vibrator can achieve
+ * across its entire frequency range.
+ *
+ * @return The maximum output acceleration in Gs.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public float getMaxOutputAccelerationGs() {
+ return mFrequencyProfile.getMaxOutputAccelerationGs();
+ }
+
+ /**
+ * Returns the frequency range (in Hz) where the vibrator can sustain at least
+ * the given minimum output acceleration (Gs).
+ *
+ * @param minOutputAccelerationGs The minimum desired output acceleration in Gs.
+ * @return A {@link Range} object representing the frequency range where the
+ * vibrator can sustain at least the given minimum acceleration, or null if
+ * the minimum output acceleration cannot be achieved.
+ *
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @Nullable
+ public Range<Float> getFrequencyRange(float minOutputAccelerationGs) {
+ return mFrequencyProfile.getFrequencyRangeHz(minOutputAccelerationGs);
+ }
+
+ /**
+ * Returns the output acceleration (in Gs) for the given frequency (Hz).
+ * This method provides the actual acceleration the vibrator will produce
+ * when operating at the specified frequency, using linear interpolation over
+ * the {@link #getFrequenciesOutputAcceleration()}.
+ *
+ * @param frequencyHz The frequency in Hz.
+ * @return The output acceleration in Gs for the given frequency.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public float getOutputAccelerationGs(float frequencyHz) {
+ return mFrequencyProfile.getOutputAccelerationGs(frequencyHz);
+ }
+
+ /**
+ * Gets the minimum frequency supported by the vibrator.
+ *
+ * @return the minimum frequency supported by the vibrator, in hertz.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public float getMinFrequencyHz() {
+ return mFrequencyProfile.getMinFrequencyHz();
+ }
+
+ /**
+ * Gets the maximum frequency supported by the vibrator.
+ *
+ * @return the maximum frequency supported by the vibrator, in hertz.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public float getMaxFrequencyHz() {
+ return mFrequencyProfile.getMaxFrequencyHz();
+ }
+
+ private static SparseArray<Float> generateFrequencyToAccelerationMap(
+ float[] frequencies, float[] accelerations) {
+ SparseArray<Float> sparseArray = new SparseArray<>(frequencies.length);
+
+ for (int i = 0; i < frequencies.length; i++) {
+ int frequency = (int) frequencies[i];
+ float acceleration = accelerations[i];
+
+ sparseArray.put(frequency,
+ Math.min(acceleration, sparseArray.get(frequency, Float.MAX_VALUE)));
+
+ }
+
+ return sparseArray;
+ }
+}
diff --git a/core/tests/vibrator/src/android/os/VibratorInfoTest.java b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
index 47d01c4..04945f3 100644
--- a/core/tests/vibrator/src/android/os/VibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
@@ -16,6 +16,8 @@
package android.os;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -24,6 +26,7 @@
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
+import android.util.Range;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,12 +42,19 @@
private static final float TEST_FREQUENCY_RESOLUTION = 25;
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+ private static final float[] TEST_FREQUENCIES =
+ new float[]{90f, 120f, 150f, 60f, 30f, 210f, 270f, 300f, 240f, 180f};
+ private static final float[] TEST_OUTPUT_ACCELERATIONS =
+ new float[]{1.2f, 1.8f, 2.4f, 0.6f, 0.1f, 2.2f, 1.0f, 0.5f, 1.9f, 3.0f};
private static final VibratorInfo.FrequencyProfileLegacy EMPTY_FREQUENCY_PROFILE =
new VibratorInfo.FrequencyProfileLegacy(Float.NaN, Float.NaN, Float.NaN, null);
private static final VibratorInfo.FrequencyProfileLegacy TEST_FREQUENCY_PROFILE_LEGACY =
new VibratorInfo.FrequencyProfileLegacy(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP);
+ private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_FREQUENCIES,
+ TEST_OUTPUT_ACCELERATIONS);
@Test
public void testHasAmplitudeControl() {
@@ -179,13 +189,123 @@
}
@Test
+ public void testGetFrequencyProfile_unsetProfileIsEmpty() {
+ assertTrue(new VibratorInfo.Builder(
+ TEST_VIBRATOR_ID).build().getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() {
+ // Invalid resonant frequency.
+ assertThat(new VibratorInfo.FrequencyProfile(Float.NaN,
+ TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isTrue();
+ assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/-1f,
+ TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isTrue();
+ // No frequency-acceleration data
+ assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null).isEmpty()).isTrue();
+ // Mismatching frequency and output acceleration lists
+ assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ new float[]{30f, 40f, 50f, 100f},
+ /*outputAccelerationsGs=*/ new float[]{0.8f, 1.0f, 2.0f}).isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testGetFrequenciesAndOutputAccelerations_noFrequencyAccelerationData_returnNull() {
+ VibratorInfo.FrequencyProfile emptyFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ assertThat(emptyFrequencyProfile.getFrequenciesHz()).isNull();
+ assertThat(emptyFrequencyProfile.getOutputAccelerationsGs()).isNull();
+ }
+
+ @Test
+ public void testGetFrequenciesAndOutputAccelerations_mismatchingDataLength_returnNull() {
+ VibratorInfo.FrequencyProfile emptyFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ new float[]{150f, 200f},
+ /*outputAccelerationsGs=*/ new float[]{1.2f, 2.2f, 3.0f});
+ assertThat(emptyFrequencyProfile.getFrequenciesHz()).isNull();
+ assertThat(emptyFrequencyProfile.getOutputAccelerationsGs()).isNull();
+ }
+
+ @Test
+ public void testGetFrequenciesAndOutputAccelerations_dataIsDedupedAndSorted() {
+ VibratorInfo.FrequencyProfile frequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ new float[]{150f, 150f, 150f, 130f, 200f, 160f},
+ /*outputAccelerationsGs=*/ new float[]{1.2f, 1.5f, 1.9f, 1.0f, 2.2f, 3.0f});
+ float[] frequencies = frequencyProfile.getFrequenciesHz();
+ assertThat(frequencies).isEqualTo(
+ new float[]{130f, 150f, 160f, 200f});
+ assertThat(frequencyProfile.getOutputAccelerationsGs()).isEqualTo(
+ new float[]{1.0f, 1.2f, 3.0f, 2.2f});
+ }
+
+ @Test
+ public void testGetFrequencyRangeHz_emptyProfileReturnsNull() {
+ VibratorInfo.FrequencyProfile emptyFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ assertThat(
+ emptyFrequencyProfile.getFrequencyRangeHz(/*minOutputAcceleration=*/0.2f)).isNull();
+ }
+
+ @Test
+ public void testGetFrequencyRangeHz_validProfileReturnsMappedValues() {
+ VibratorInfo.FrequencyProfile frequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/new float[]{90f, 120f, 150f, 60f, 30f, 210f, 180f},
+ /*outputAccelerationsGs=*/ new float[]{1.2f, 1.8f, 2.4f, 0.6f, 0.4f, 2.2f, 3.0f});
+
+ // lower and upper bounds are min and max frequencies
+ assertThat(frequencyProfile.getFrequencyRangeHz(/*minOutputAcceleration=*/0.33f)).isEqualTo(
+ new Range<>(frequencyProfile.getMinFrequencyHz(),
+ frequencyProfile.getMaxFrequencyHz()));
+
+ // lower and upper bounds are within frequency range and use interpolation
+ assertThat(frequencyProfile.getFrequencyRangeHz(/*minOutputAcceleration=*/2.6f))
+ .isEqualTo(new Range<>(160f, 195f));
+
+ // upper bound is max frequency
+ assertThat(frequencyProfile.getFrequencyRangeHz(/*minOutputAcceleration=*/2.0f))
+ .isEqualTo(new Range<>(130f, frequencyProfile.getMaxFrequencyHz()));
+ }
+
+ @Test
+ public void testFrequencyProfile_emptyProfileReturnsNanValues() {
+ VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
+ /*resonantFrequencyHz=*/150f, /*frequenciesHz=*/ null,
+ /*outputAccelerationsGs=*/ null);
+
+ assertThat(frequencyProfile.getMaxOutputAccelerationGs()).isNaN();
+ assertThat(frequencyProfile.getMinFrequencyHz()).isNaN();
+ assertThat(frequencyProfile.getMaxFrequencyHz()).isNaN();
+ assertThat(frequencyProfile.getOutputAccelerationGs(/*frequencyHz=*/150f)).isNaN();
+ }
+
+ @Test
+ public void testFrequencyProfile_validProfileReturnsAppropriateValues() {
+ VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
+ /*resonantFrequencyHz=*/150f, TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS);
+
+ assertThat(frequencyProfile.getMaxOutputAccelerationGs()).isEqualTo(3f);
+ assertThat(frequencyProfile.getMinFrequencyHz()).isEqualTo(30f);
+ assertThat(frequencyProfile.getMaxFrequencyHz()).isEqualTo(300f);
+ assertThat(frequencyProfile.getOutputAccelerationGs(/*frequencyHz=*/150f)).isEqualTo(2.4f);
+ // Test getting output acceleration using linear interpolation
+ assertThat(frequencyProfile.getOutputAccelerationGs(/*frequencyHz=*/166f)).isEqualTo(
+ 2.72f);
+ }
+
+ @Test
public void testGetFrequencyProfileLegacy_unsetProfileIsEmpty() {
assertTrue(new VibratorInfo.Builder(
TEST_VIBRATOR_ID).build().getFrequencyProfileLegacy().isEmpty());
}
@Test
- public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() {
+ public void testFrequencyProfileLegacy_invalidValuesCreatesEmptyProfile() {
// Invalid, contains NaN values or empty array.
assertTrue(new VibratorInfo.FrequencyProfileLegacy(
Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).isEmpty());
@@ -216,7 +336,7 @@
}
@Test
- public void testGetFrequencyRangeHz_emptyProfileReturnsNull() {
+ public void testLegacyGetFrequencyRangeHz_emptyProfileReturnsNull() {
assertNull(new VibratorInfo.FrequencyProfileLegacy(
Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz());
assertNull(new VibratorInfo.FrequencyProfileLegacy(
@@ -228,7 +348,7 @@
}
@Test
- public void testGetFrequencyRangeHz_validProfileReturnsMappedValues() {
+ public void testLegacyGetFrequencyRangeHz_validProfileReturnsMappedValues() {
VibratorInfo.FrequencyProfileLegacy profile = new VibratorInfo.FrequencyProfileLegacy(
/* resonantFrequencyHz= */ 150,
/* minFrequencyHz= */ 50,
@@ -306,6 +426,7 @@
.setPwleSizeMax(20)
.setQFactor(2f)
.setFrequencyProfileLegacy(TEST_FREQUENCY_PROFILE_LEGACY)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.setMaxEnvelopeEffectSize(16)
.setMinEnvelopeEffectControlPointDurationMillis(20)
.setMaxEnvelopeEffectControlPointDurationMillis(1_000);
@@ -347,18 +468,33 @@
assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
assertFalse(complete.equalContent(completeWithDifferentPrimitiveDuration));
- VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
+ VibratorInfo completeWithDifferentFrequencyProfileLegacy = completeBuilder
.setFrequencyProfileLegacy(new VibratorInfo.FrequencyProfileLegacy(
TEST_RESONANT_FREQUENCY + 20,
TEST_MIN_FREQUENCY + 10,
TEST_FREQUENCY_RESOLUTION + 5,
TEST_AMPLITUDE_MAP))
.build();
+ assertNotEquals(complete, completeWithDifferentFrequencyProfileLegacy);
+ assertFalse(complete.equalContent(completeWithDifferentFrequencyProfileLegacy));
+
+ VibratorInfo completeWithEmptyFrequencyProfileLegacy = completeBuilder
+ .setFrequencyProfileLegacy(EMPTY_FREQUENCY_PROFILE)
+ .build();
+ assertNotEquals(complete, completeWithEmptyFrequencyProfileLegacy);
+ assertFalse(complete.equalContent(completeWithEmptyFrequencyProfileLegacy));
+
+ VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
+ .setFrequencyProfile(
+ new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY + 20,
+ new float[]{90f, 150f}, new float[]{1.2f, 2.2f}))
+ .build();
assertNotEquals(complete, completeWithDifferentFrequencyProfile);
assertFalse(complete.equalContent(completeWithDifferentFrequencyProfile));
VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder
- .setFrequencyProfileLegacy(EMPTY_FREQUENCY_PROFILE)
+ .setFrequencyProfile(
+ new VibratorInfo.FrequencyProfile(Float.NaN, null, null))
.build();
assertNotEquals(complete, completeWithEmptyFrequencyProfile);
assertFalse(complete.equalContent(completeWithEmptyFrequencyProfile));
@@ -396,6 +532,7 @@
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setQFactor(Float.NaN)
.setFrequencyProfileLegacy(TEST_FREQUENCY_PROFILE_LEGACY)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
Parcel parcel = Parcel.obtain();
diff --git a/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java b/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
index f192b89..c9ab297 100644
--- a/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
@@ -16,6 +16,8 @@
package android.os.vibrator;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
@@ -24,7 +26,11 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -33,6 +39,9 @@
public class MultiVibratorInfoTest {
private static final float TEST_TOLERANCE = 1e-5f;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testGetId() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
@@ -157,6 +166,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetQFactorAndResonantFrequency_differentValues_returnsNaN() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -187,6 +197,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetQFactorAndResonantFrequency_sameValues_returnsValue() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -212,6 +223,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetFrequencyProfileLegacy_differentResonantFreqOrResolutions_returnsEmpty() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -240,6 +252,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetFrequencyProfileLegacy_missingValues_returnsEmpty() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -288,6 +301,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetFrequencyProfileLegacy_unalignedMaxAmplitudes_returnsEmpty() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -312,6 +326,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetFrequencyProfileLegacy_alignedProfiles_returnsIntersection() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -353,6 +368,132 @@
assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
}
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_alignedProfiles_returnsIntersection() {
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{30f, 60f, 120f, 150f, 180f, 210f, 270f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 1.8f, 2.4f, 3.0f, 2.2f, 1.0f, 0.5f});
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{120f, 150f, 180f, 210f},
+ /*accelerations=*/new float[]{1.5f, 2.6f, 2.7f, 2.1f});
+
+ VibratorInfo.FrequencyProfile expectedFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/
+ 180f, /*frequenciesHz=*/new float[]{120.0f, 150.0f, 180.0f, 210.0f},
+ /*outputAccelerationsGs=*/new float[]{1.5f, 2.4f, 2.7f, 2.1f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(expectedFrequencyProfile);
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_alignedProfilesUsingInterpolation_returnsIntersection() {
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{30f, 60f, 120f},
+ /*accelerations=*/new float[]{0.25f, 1.0f, 4.0f});
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{40f, 70f, 110f},
+ /*accelerations=*/new float[]{1.0f, 2.5f, 4.0f});
+
+ VibratorInfo.FrequencyProfile expectedFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/
+ 180f, /*frequenciesHz=*/new float[]{40f, 60f, 70f, 110f},
+ /*outputAccelerationsGs=*/new float[]{0.5f, 1.0f, 1.5f, 3.5f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(expectedFrequencyProfile);
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_disjointFrequencyRange_returnsEmpty() {
+
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{30f, 60f, 120f, 150f, 180f, 210f, 270f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 1.8f, 2.4f, 3.0f, 2.2f, 1.0f, 0.5f});
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{310f, 320f, 350f, 380f, 410f, 440f},
+ /*accelerations=*/new float[]{0.3f, 0.75f, 1.82f, 2.11f, 2.8f, 2.12f, 1.4f, 0.42f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/ Float.NaN,
+ /*frequenciesHz=*/null, /*outputAccelerationsGs=*/null));
+ assertThat(info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_emptyFrequencyRange_returnsEmpty() {
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/180f,
+ /*frequencies=*/null, /*accelerations=*/null);
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/180f,
+ /*frequencies=*/new float[]{30f, 60f, 150f, 180f, 210f, 240f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 2.4f, 3.0f, 2.2f, 1.9f, 0.5f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/ Float.NaN,
+ /*frequenciesHz=*/null,
+ /*outputAccelerationsGs=*/null));
+ assertThat(info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_differentResonantFrequency_returnsEmpty() {
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 160f,
+ /*frequencies=*/new float[]{30f, 60f, 120f, 150f, 180f, 210f, 270f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 1.8f, 2.4f, 3.0f, 2.2f, 1.0f, 0.5f});
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{30f, 60f, 120f, 150f, 180f, 210f, 270f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 1.8f, 2.4f, 3.0f, 2.2f, 1.0f, 0.5f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/ Float.NaN,
+ /*frequenciesHz=*/null,
+ /*outputAccelerationsGs=*/null));
+ assertThat(info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)).isFalse();
+ }
+
+ private VibratorInfo createVibratorInfoWithFrequencyProfile(int id, long capabilities,
+ float resonantFrequencyHz, float[] frequencies, float[] accelerations) {
+ return new VibratorInfo.Builder(id)
+ .setCapabilities(capabilities)
+ .setFrequencyProfile(
+ new VibratorInfo.FrequencyProfile(resonantFrequencyHz, frequencies,
+ accelerations))
+ .build();
+ }
+
/**
* Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
*/
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index bcd0b94..903d892 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -43,6 +43,8 @@
static jmethodID sMethodIdOnComplete;
static jclass sFrequencyProfileLegacyClass;
static jmethodID sFrequencyProfileLegacyCtor;
+static jclass sFrequencyProfileClass;
+static jmethodID sFrequencyProfileCtor;
static struct {
jmethodID setCapabilities;
jmethodID setSupportedEffects;
@@ -54,6 +56,7 @@
jmethodID setCompositionSizeMax;
jmethodID setQFactor;
jmethodID setFrequencyProfileLegacy;
+ jmethodID setFrequencyProfile;
jmethodID setMaxEnvelopeEffectSize;
jmethodID setMinEnvelopeEffectControlPointDurationMillis;
jmethodID setMaxEnvelopeEffectControlPointDurationMillis;
@@ -524,6 +527,40 @@
sVibratorInfoBuilderClassInfo.setFrequencyProfileLegacy,
frequencyProfileLegacy);
+ if (info.frequencyToOutputAccelerationMap.isOk()) {
+ size_t mapSize = info.frequencyToOutputAccelerationMap.value().size();
+
+ jfloatArray frequenciesHz = env->NewFloatArray(mapSize);
+ jfloatArray outputAccelerationsGs = env->NewFloatArray(mapSize);
+
+ jfloat* frequenciesHzPtr = env->GetFloatArrayElements(frequenciesHz, nullptr);
+ jfloat* outputAccelerationsGsPtr =
+ env->GetFloatArrayElements(outputAccelerationsGs, nullptr);
+
+ size_t i = 0;
+ for (auto const& dataEntry : info.frequencyToOutputAccelerationMap.value()) {
+ frequenciesHzPtr[i] = static_cast<jfloat>(dataEntry.frequencyHz);
+ outputAccelerationsGsPtr[i] = static_cast<jfloat>(dataEntry.maxOutputAccelerationGs);
+ i++;
+ }
+
+ // Release the float pointers
+ env->ReleaseFloatArrayElements(frequenciesHz, frequenciesHzPtr, 0);
+ env->ReleaseFloatArrayElements(outputAccelerationsGs, outputAccelerationsGsPtr, 0);
+
+ jobject frequencyProfile =
+ env->NewObject(sFrequencyProfileClass, sFrequencyProfileCtor, resonantFrequency,
+ frequenciesHz, outputAccelerationsGs);
+
+ env->CallObjectMethod(vibratorInfoBuilder,
+ sVibratorInfoBuilderClassInfo.setFrequencyProfile, frequencyProfile);
+
+ // Delete local references to avoid memory leaks
+ env->DeleteLocalRef(frequenciesHz);
+ env->DeleteLocalRef(outputAccelerationsGs);
+ env->DeleteLocalRef(frequencyProfile);
+ }
+
return info.shouldRetry() ? JNI_FALSE : JNI_TRUE;
}
@@ -574,6 +611,10 @@
sFrequencyProfileLegacyCtor =
GetMethodIDOrDie(env, sFrequencyProfileLegacyClass, "<init>", "(FFF[F)V");
+ jclass frequencyProfileClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfile");
+ sFrequencyProfileClass = static_cast<jclass>(env->NewGlobalRef(frequencyProfileClass));
+ sFrequencyProfileCtor = GetMethodIDOrDie(env, sFrequencyProfileClass, "<init>", "(F[F[F)V");
+
jclass vibratorInfoBuilderClass = FindClassOrDie(env, "android/os/VibratorInfo$Builder");
sVibratorInfoBuilderClassInfo.setCapabilities =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setCapabilities",
@@ -606,6 +647,10 @@
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfileLegacy",
"(Landroid/os/VibratorInfo$FrequencyProfileLegacy;)"
"Landroid/os/VibratorInfo$Builder;");
+ sVibratorInfoBuilderClassInfo.setFrequencyProfile =
+ GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfile",
+ "(Landroid/os/VibratorInfo$FrequencyProfile;)"
+ "Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setMaxEnvelopeEffectSize =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setMaxEnvelopeEffectSize",
"(I)Landroid/os/VibratorInfo$Builder;");
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index 1493253..d7ae046 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -35,7 +35,9 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import androidx.test.core.app.ApplicationProvider;
@@ -63,6 +65,8 @@
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private PackageManagerInternal mPackageManagerInternalMock;
@@ -186,6 +190,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testStepAndRampSegments_withValidFreqMapping_returnsClippedValuesOnlyInRamps() {
VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
// Individual step without frequency control, will not use PWLE composition
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
index 8103682..96f0fda2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -26,8 +26,11 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -49,6 +52,9 @@
private RampToStepAdapter mAdapter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mAdapter = new RampToStepAdapter(TEST_STEP_DURATION);
@@ -87,6 +93,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testRampSegments_withoutPwleCapability_convertsRampsToSteps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
index f2c3726..53e49e0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
@@ -26,8 +26,11 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -52,6 +55,9 @@
private SplitSegmentsAdapter mAdapter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mAdapter = new SplitSegmentsAdapter();
@@ -97,6 +103,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testRampSegments_withPwleDurationLimit_splitsLongRampsAndPreserveOtherSegments() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
index d501dba..fae634d 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -26,8 +26,11 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -48,6 +51,9 @@
private StepToRampAdapter mAdapter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mAdapter = new StepToRampAdapter();
@@ -134,6 +140,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testStepSegments_withPwleCapabilityAndFrequency_convertsStepsToRamps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 100, /* duration= */ 10),
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 7536f5f..58a1e84 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -63,9 +63,11 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.SparseArray;
@@ -113,6 +115,8 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -780,6 +784,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
@@ -870,6 +875,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_runsComposePwle() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
@@ -1724,6 +1730,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
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 f96177d..6dc1b10 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -76,6 +76,9 @@
private float mFrequencyResolution = Float.NaN;
private float mQFactor = Float.NaN;
private float[] mMaxAmplitudes;
+
+ private float[] mFrequenciesHz;
+ private float[] mOutputAccelerationsGs;
private long mVendorEffectDuration = EFFECT_DURATION;
void recordEffectSegment(long vibrationId, VibrationEffectSegment segment) {
@@ -220,6 +223,9 @@
infoBuilder.setQFactor(mQFactor);
infoBuilder.setFrequencyProfileLegacy(new VibratorInfo.FrequencyProfileLegacy(
mResonantFrequency, mMinFrequency, mFrequencyResolution, mMaxAmplitudes));
+ infoBuilder.setFrequencyProfile(
+ new VibratorInfo.FrequencyProfile(mResonantFrequency, mFrequenciesHz,
+ mOutputAccelerationsGs));
infoBuilder.setMaxEnvelopeEffectSize(mMaxEnvelopeEffectSize);
infoBuilder.setMinEnvelopeEffectControlPointDurationMillis(
mMinEnvelopeEffectControlPointDurationMillis);
@@ -360,6 +366,16 @@
mMaxAmplitudes = maxAmplitudes;
}
+ /** Set the list of available frequencies. */
+ public void setFrequenciesHz(float[] frequenciesHz) {
+ mFrequenciesHz = frequenciesHz;
+ }
+
+ /** Set the max output acceleration achievable by the supported frequencies. */
+ public void setOutputAccelerationsGs(float[] outputAccelerationsGs) {
+ mOutputAccelerationsGs = outputAccelerationsGs;
+ }
+
/** Set the duration of vendor effects in fake vibrator hardware. */
public void setVendorEffectDuration(long durationMs) {
mVendorEffectDuration = durationMs;