diff --git a/core/api/current.txt b/core/api/current.txt index 0c19f89d9546a..e04e14b90f6bc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32955,9 +32955,13 @@ package android.os { method @NonNull public int[] areEffectsSupported(@NonNull int...); method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...); method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); + method @Nullable public android.os.vibrator.VibratorFrequencyProfile getFrequencyProfile(); method public int getId(); method @NonNull public int[] getPrimitiveDurations(@NonNull int...); + method public float getQFactor(); + method public float getResonantFrequency(); method public abstract boolean hasAmplitudeControl(); + method public boolean hasFrequencyControl(); method public abstract boolean hasVibrator(); method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long); method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long, android.media.AudioAttributes); @@ -33283,6 +33287,17 @@ package android.os.strictmode { } +package android.os.vibrator { + + public final class VibratorFrequencyProfile { + method public float getMaxAmplitudeMeasurementInterval(); + method @FloatRange(from=0, to=1) @NonNull public float[] getMaxAmplitudeMeasurements(); + method public float getMaxFrequency(); + method public float getMinFrequency(); + } + +} + package android.preference { @Deprecated public class CheckBoxPreference extends android.preference.TwoStatePreference { diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 003776175f266..0aafaf456a083 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -18,18 +18,25 @@ package android.os; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.util.ArrayMap; import android.util.Log; +import android.util.Range; +import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.function.Function; /** * Vibrator implementation that controls the main system vibrator. @@ -51,7 +58,7 @@ public class SystemVibrator extends Vibrator { private final Object mLock = new Object(); @GuardedBy("mLock") - private AllVibratorsInfo mVibratorInfo; + private VibratorInfo mVibratorInfo; @UnsupportedAppUsage public SystemVibrator(Context context) { @@ -71,6 +78,11 @@ public class SystemVibrator extends Vibrator { return VibratorInfo.EMPTY_VIBRATOR_INFO; } int[] vibratorIds = mVibratorManager.getVibratorIds(); + if (vibratorIds.length == 0) { + // It is known that the device has no vibrator, so cache and return info that + // reflects the lack of support for effects/primitives. + return mVibratorInfo = new NoVibratorInfo(); + } VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length]; for (int i = 0; i < vibratorIds.length; i++) { Vibrator vibrator = mVibratorManager.getVibrator(vibratorIds[i]); @@ -83,7 +95,12 @@ public class SystemVibrator extends Vibrator { } vibratorInfos[i] = vibrator.getInfo(); } - return mVibratorInfo = new AllVibratorsInfo(vibratorInfos); + if (vibratorInfos.length == 1) { + // Device has a single vibrator info, cache and return successfully loaded info. + return mVibratorInfo = new VibratorInfo(/* id= */ -1, vibratorInfos[0]); + } + // Device has multiple vibrators, generate a single info representing all of them. + return mVibratorInfo = new MultiVibratorInfo(vibratorInfos); } } @@ -257,77 +274,282 @@ public class SystemVibrator extends Vibrator { } /** - * Represents all the vibrators information as a single {@link VibratorInfo}. + * Represents a device with no vibrator as a single {@link VibratorInfo}. * - *
This uses the first vibrator on the list as the default one for all hardware spec, but - * uses an intersection of all vibrators to decide the capabilities and effect/primitive + * @hide + */ + @VisibleForTesting + public static class NoVibratorInfo extends VibratorInfo { + public NoVibratorInfo() { + // Use empty arrays to indicate no support, while null would indicate support unknown. + super(/* id= */ -1, + /* capabilities= */ 0, + /* supportedEffects= */ new SparseBooleanArray(), + /* supportedBraking= */ new SparseBooleanArray(), + /* supportedPrimitives= */ new SparseIntArray(), + /* primitiveDelayMax= */ 0, + /* compositionSizeMax= */ 0, + /* pwlePrimitiveDurationMax= */ 0, + /* pwleSizeMax= */ 0, + /* qFactor= */ Float.NaN, + new FrequencyProfile(/* resonantFrequencyHz= */ Float.NaN, + /* minFrequencyHz= */ Float.NaN, + /* frequencyResolutionHz= */ Float.NaN, + /* maxAmplitudes= */ null)); + } + } + + /** + * Represents multiple vibrator information as a single {@link VibratorInfo}. + * + *
This uses an intersection of all vibrators to decide the capabilities and effect/primitive
* support.
*
* @hide
*/
@VisibleForTesting
- public static class AllVibratorsInfo extends VibratorInfo {
- private final VibratorInfo[] mVibratorInfos;
+ public static class MultiVibratorInfo extends VibratorInfo {
+ // Epsilon used for float comparison applied in calculations for the merged info.
+ private static final float EPSILON = 1e-5f;
- public AllVibratorsInfo(VibratorInfo[] vibrators) {
- super(/* id= */ -1, capabilitiesIntersection(vibrators),
- vibrators.length > 0 ? vibrators[0] : VibratorInfo.EMPTY_VIBRATOR_INFO);
- mVibratorInfos = vibrators;
- }
-
- @Override
- public int isEffectSupported(int effectId) {
- if (mVibratorInfos.length == 0) {
- return Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
- }
- int supported = Vibrator.VIBRATION_EFFECT_SUPPORT_YES;
- for (VibratorInfo info : mVibratorInfos) {
- int effectSupported = info.isEffectSupported(effectId);
- if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) {
- return effectSupported;
- } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
- supported = effectSupported;
- }
- }
- return supported;
- }
-
- @Override
- public boolean isPrimitiveSupported(int primitiveId) {
- if (mVibratorInfos.length == 0) {
- return false;
- }
- for (VibratorInfo info : mVibratorInfos) {
- if (!info.isPrimitiveSupported(primitiveId)) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public int getPrimitiveDuration(int primitiveId) {
- int maxDuration = 0;
- for (VibratorInfo info : mVibratorInfos) {
- int duration = info.getPrimitiveDuration(primitiveId);
- if (duration == 0) {
- return 0;
- }
- maxDuration = Math.max(maxDuration, duration);
- }
- return maxDuration;
+ public MultiVibratorInfo(VibratorInfo[] vibrators) {
+ super(/* id= */ -1,
+ capabilitiesIntersection(vibrators),
+ supportedEffectsIntersection(vibrators),
+ supportedBrakingIntersection(vibrators),
+ supportedPrimitivesAndDurationsIntersection(vibrators),
+ integerLimitIntersection(vibrators, VibratorInfo::getPrimitiveDelayMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getCompositionSizeMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
+ floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
+ frequencyProfileIntersection(vibrators));
}
private static int capabilitiesIntersection(VibratorInfo[] infos) {
- if (infos.length == 0) {
- return 0;
- }
int intersection = ~0;
for (VibratorInfo info : infos) {
intersection &= info.getCapabilities();
}
return intersection;
}
+
+ @Nullable
+ private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) {
+ for (VibratorInfo info : infos) {
+ if (!info.isBrakingSupportKnown()) {
+ // If one vibrator support is unknown, then the intersection is also unknown.
+ return null;
+ }
+ }
+
+ SparseBooleanArray intersection = new SparseBooleanArray();
+ SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking();
+
+ brakingIdLoop:
+ for (int i = 0; i < firstVibratorBraking.size(); i++) {
+ int brakingId = firstVibratorBraking.keyAt(i);
+ if (!firstVibratorBraking.valueAt(i)) {
+ // The first vibrator already doesn't support this braking, so skip it.
+ continue brakingIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ if (!infos[j].hasBrakingSupport(brakingId)) {
+ // One vibrator doesn't support this braking, so the intersection doesn't.
+ continue brakingIdLoop;
+ }
+ }
+
+ intersection.put(brakingId, true);
+ }
+
+ return intersection;
+ }
+
+ @Nullable
+ private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) {
+ for (VibratorInfo info : infos) {
+ if (!info.isEffectSupportKnown()) {
+ // If one vibrator support is unknown, then the intersection is also unknown.
+ return null;
+ }
+ }
+
+ SparseBooleanArray intersection = new SparseBooleanArray();
+ SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects();
+
+ effectIdLoop:
+ for (int i = 0; i < firstVibratorEffects.size(); i++) {
+ int effectId = firstVibratorEffects.keyAt(i);
+ if (!firstVibratorEffects.valueAt(i)) {
+ // The first vibrator already doesn't support this effect, so skip it.
+ continue effectIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ if (infos[j].isEffectSupported(effectId) != VIBRATION_EFFECT_SUPPORT_YES) {
+ // One vibrator doesn't support this effect, so the intersection doesn't.
+ continue effectIdLoop;
+ }
+ }
+
+ intersection.put(effectId, true);
+ }
+
+ return intersection;
+ }
+
+ @NonNull
+ private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
+ VibratorInfo[] infos) {
+ SparseIntArray intersection = new SparseIntArray();
+ SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
+
+ primitiveIdLoop:
+ for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
+ int primitiveId = firstVibratorPrimitives.keyAt(i);
+ int primitiveDuration = firstVibratorPrimitives.valueAt(i);
+ if (primitiveDuration == 0) {
+ // The first vibrator already doesn't support this primitive, so skip it.
+ continue primitiveIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
+ if (vibratorPrimitiveDuration == 0) {
+ // One vibrator doesn't support this primitive, so the intersection doesn't.
+ continue primitiveIdLoop;
+ } else {
+ // The primitive vibration duration is the maximum among all vibrators.
+ primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
+ }
+ }
+
+ intersection.put(primitiveId, primitiveDuration);
+ }
+ return intersection;
+ }
+
+ private static int integerLimitIntersection(VibratorInfo[] infos,
+ Function The profile describes the relative 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.
+ */
+ @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.
*
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 5271c4df11ef0..d162c74aeea2a 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -16,7 +16,6 @@
package android.os;
-import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.vibrator.Braking;
@@ -26,6 +25,8 @@ import android.util.Range;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -56,7 +57,7 @@ public class VibratorInfo implements Parcelable {
private final int mPwlePrimitiveDurationMax;
private final int mPwleSizeMax;
private final float mQFactor;
- private final FrequencyMapping mFrequencyMapping;
+ private final FrequencyProfile mFrequencyProfile;
VibratorInfo(Parcel in) {
mId = in.readInt();
@@ -69,7 +70,15 @@ public class VibratorInfo implements Parcelable {
mPwlePrimitiveDurationMax = in.readInt();
mPwleSizeMax = in.readInt();
mQFactor = in.readFloat();
- mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader(), android.os.VibratorInfo.FrequencyMapping.class);
+ mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in);
+ }
+
+ public VibratorInfo(int id, @NonNull VibratorInfo baseVibratorInfo) {
+ this(id, baseVibratorInfo.mCapabilities, baseVibratorInfo.mSupportedEffects,
+ baseVibratorInfo.mSupportedBraking, baseVibratorInfo.mSupportedPrimitives,
+ baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax,
+ baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax,
+ baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfile);
}
/**
@@ -92,7 +101,7 @@ public class VibratorInfo implements Parcelable {
* @param pwleSizeMax The maximum number of primitives supported by a PWLE
* composition.
* @param qFactor The vibrator quality factor.
- * @param frequencyMapping The description of the vibrator supported frequencies and max
+ * @param frequencyProfile The description of the vibrator supported frequencies and max
* amplitude mappings.
* @hide
*/
@@ -100,7 +109,9 @@ public class VibratorInfo implements Parcelable {
@Nullable SparseBooleanArray supportedBraking,
@NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
- float qFactor, @NonNull FrequencyMapping frequencyMapping) {
+ float qFactor, @NonNull FrequencyProfile frequencyProfile) {
+ Preconditions.checkNotNull(supportedPrimitives);
+ Preconditions.checkNotNull(frequencyProfile);
mId = id;
mCapabilities = capabilities;
mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
@@ -111,14 +122,7 @@ public class VibratorInfo implements Parcelable {
mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
mPwleSizeMax = pwleSizeMax;
mQFactor = qFactor;
- mFrequencyMapping = frequencyMapping;
- }
-
- protected VibratorInfo(int id, int capabilities, VibratorInfo baseVibrator) {
- this(id, capabilities, baseVibrator.mSupportedEffects, baseVibrator.mSupportedBraking,
- baseVibrator.mSupportedPrimitives, baseVibrator.mPrimitiveDelayMax,
- baseVibrator.mCompositionSizeMax, baseVibrator.mPwlePrimitiveDurationMax,
- baseVibrator.mPwleSizeMax, baseVibrator.mQFactor, baseVibrator.mFrequencyMapping);
+ mFrequencyProfile = frequencyProfile;
}
@Override
@@ -133,7 +137,7 @@ public class VibratorInfo implements Parcelable {
dest.writeInt(mPwlePrimitiveDurationMax);
dest.writeInt(mPwleSizeMax);
dest.writeFloat(mQFactor);
- dest.writeParcelable(mFrequencyMapping, flags);
+ mFrequencyProfile.writeToParcel(dest, flags);
}
@Override
@@ -170,13 +174,13 @@ public class VibratorInfo implements Parcelable {
&& Objects.equals(mSupportedEffects, that.mSupportedEffects)
&& Objects.equals(mSupportedBraking, that.mSupportedBraking)
&& Objects.equals(mQFactor, that.mQFactor)
- && Objects.equals(mFrequencyMapping, that.mFrequencyMapping);
+ && Objects.equals(mFrequencyProfile, that.mFrequencyProfile);
}
@Override
public int hashCode() {
int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
- mQFactor, mFrequencyMapping);
+ mQFactor, mFrequencyProfile);
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
@@ -198,7 +202,7 @@ public class VibratorInfo implements Parcelable {
+ ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax
+ ", mPwleSizeMax=" + mPwleSizeMax
+ ", mQFactor=" + mQFactor
- + ", mFrequencyMapping=" + mFrequencyMapping
+ + ", mFrequencyProfile=" + mFrequencyProfile
+ '}';
}
@@ -234,6 +238,30 @@ public class VibratorInfo implements Parcelable {
return Braking.NONE;
}
+ /** @hide */
+ @Nullable
+ public SparseBooleanArray getSupportedBraking() {
+ if (mSupportedBraking == null) {
+ return null;
+ }
+ return mSupportedBraking.clone();
+ }
+
+ /** @hide */
+ public boolean isBrakingSupportKnown() {
+ return mSupportedBraking != null;
+ }
+
+ /** @hide */
+ public boolean hasBrakingSupport(@Braking int braking) {
+ return (mSupportedBraking != null) && mSupportedBraking.get(braking);
+ }
+
+ /** @hide */
+ public boolean isEffectSupportKnown() {
+ return mSupportedEffects != null;
+ }
+
/**
* Query whether the vibrator supports the given effect.
*
@@ -252,6 +280,15 @@ public class VibratorInfo implements Parcelable {
: Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
}
+ /** @hide */
+ @Nullable
+ public SparseBooleanArray getSupportedEffects() {
+ if (mSupportedEffects == null) {
+ return null;
+ }
+ return mSupportedEffects.clone();
+ }
+
/**
* Query whether the vibrator supports the given primitive.
*
@@ -276,6 +313,11 @@ public class VibratorInfo implements Parcelable {
return mSupportedPrimitives.get(primitiveId);
}
+ /** @hide */
+ public SparseIntArray getSupportedPrimitives() {
+ return mSupportedPrimitives.clone();
+ }
+
/**
* Query the maximum delay supported for a primitive in a composed effect.
*
@@ -329,8 +371,8 @@ public class VibratorInfo implements Parcelable {
* @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
* this vibrator is a composite of multiple physical devices.
*/
- public float getResonantFrequency() {
- return mFrequencyMapping.mResonantFrequencyHz;
+ public float getResonantFrequencyHz() {
+ return mFrequencyProfile.mResonantFrequencyHz;
}
/**
@@ -344,31 +386,14 @@ public class VibratorInfo implements Parcelable {
}
/**
- * Return a range of frequency values supported by the vibrator.
+ * Gets the profile of supported frequencies, including the measurements of maximum relative
+ * output acceleration for supported vibration frequencies.
*
- * @return A range of frequency values supported, in hertz. The range will always contain the
- * device resonant frequency. Devices without frequency control will return null.
- * @hide
+ * If the devices does not have frequency control then the profile should be empty.
*/
- @Nullable
- public Range This mapping is defined by the following parameters:
+ * This profile is defined by the following parameters:
*
* The profile contains the minimum and maximum supported vibration frequencies, if the device
+ * supports independent frequency control.
+ *
+ * It also describes the relative output acceleration of a vibration at different supported
+ * frequencies. The acceleration is defined by a relative amplitude value between 0 and 1,
+ * inclusive, where 0 represents the vibrator off state and 1 represents the maximum output
+ * acceleration that the vibrator can reach across all supported frequencies.
+ *
+ * The measurements are returned as an array of uniformly distributed amplitude values for
+ * frequencies between the minimum and maximum supported ones. The measurement interval is the
+ * frequency increment between each pair of amplitude values.
+ *
+ * Vibrators without independent frequency control do not have a frequency profile.
+ */
+public final class VibratorFrequencyProfile {
+
+ private final VibratorInfo.FrequencyProfile mFrequencyProfile;
+
+ /** @hide */
+ public VibratorFrequencyProfile(@NonNull VibratorInfo.FrequencyProfile frequencyProfile) {
+ Preconditions.checkArgument(!frequencyProfile.isEmpty(),
+ "Frequency profile must have a non-empty frequency range");
+ mFrequencyProfile = frequencyProfile;
+ }
+
+ /**
+ * Measurements of the maximum relative amplitude the vibrator can achieve for each supported
+ * frequency.
+ *
+ * The frequency of a measurement is determined as:
+ *
+ * {@code getMinFrequency() + measurementIndex * getMaxAmplitudeMeasurementInterval()}
+ *
+ * The returned list will not be empty, and will have entries representing frequencies from
+ * {@link #getMinFrequency()} to {@link #getMaxFrequency()}, inclusive.
+ *
+ * @return Array of maximum relative amplitude measurements, each value is between 0 and 1,
+ * inclusive.
+ */
+ @NonNull
+ @FloatRange(from = 0, to = 1)
+ public float[] getMaxAmplitudeMeasurements() {
+ // VibratorInfo getters always return a copy or clone of the data objects.
+ return mFrequencyProfile.getMaxAmplitudes();
+ }
+
+ /**
+ * Gets the frequency interval used to measure the maximum relative amplitudes.
+ *
+ * @return the frequency interval used for the measurement, in hertz.
+ */
+ public float getMaxAmplitudeMeasurementInterval() {
+ return mFrequencyProfile.getFrequencyResolutionHz();
+ }
+
+ /**
+ * Gets the minimum frequency supported by the vibrator.
+ *
+ * @return the minimum frequency supported by the vibrator, in hertz.
+ */
+ public float getMinFrequency() {
+ return mFrequencyProfile.getFrequencyRangeHz().getLower();
+ }
+
+ /**
+ * Gets the maximum frequency supported by the vibrator.
+ *
+ * @return the maximum frequency supported by the vibrator, in hertz.
+ */
+ public float getMaxFrequency() {
+ return mFrequencyProfile.getFrequencyRangeHz().getUpper();
+ }
+}
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java
index d0e03a24427e1..84253e6a33c4b 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -19,13 +19,11 @@ package android.os;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
import android.platform.test.annotations.Presubmit;
-import android.util.Range;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,10 +41,10 @@ public class VibratorInfoTest {
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 VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING =
- new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null);
- private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
- new VibratorInfo.FrequencyMapping(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
+ private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
+ private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP);
@Test
@@ -142,95 +140,76 @@ public class VibratorInfoTest {
}
@Test
- public void testGetFrequencyRangeHz_invalidFrequencyMappingReturnsNull() {
+ public void testGetFrequencyProfile_unsetProfileIsEmpty() {
+ assertTrue(
+ new VibratorInfo.Builder(TEST_VIBRATOR_ID).build().getFrequencyProfile().isEmpty());
+ }
+ @Test
+ public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() {
// Invalid, contains NaN values or empty array.
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID).build().getFrequencyRangeHz());
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- Float.NaN, 50, 25, TEST_AMPLITUDE_MAP))
- .build().getFrequencyRangeHz());
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- 150, Float.NaN, 25, TEST_AMPLITUDE_MAP))
- .build().getFrequencyRangeHz());
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- 150, 50, Float.NaN, TEST_AMPLITUDE_MAP))
- .build().getFrequencyRangeHz());
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(150, 50, 25, null))
- .build().getFrequencyRangeHz());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, Float.NaN, 25, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, 50, Float.NaN, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(150, 50, 25, null).isEmpty());
+ // Invalid, contains zero or negative frequency values.
+ assertTrue(new VibratorInfo.FrequencyProfile(-1, 50, 25, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(150, 0, 25, TEST_AMPLITUDE_MAP).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(150, 50, -2, TEST_AMPLITUDE_MAP).isEmpty());
+ // Invalid max amplitude entries.
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, 50, 50, new float[] { -1, 0, 1, 1, 0 }).isEmpty());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, 50, 50, new float[] { 0, 1, 2, 1, 0 }).isEmpty());
// Invalid, minFrequency > resonantFrequency
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 250, 25, null))
- .build().getFrequencyRangeHz());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 250, 25, TEST_AMPLITUDE_MAP)
+ .isEmpty());
// Invalid, maxFrequency < resonantFrequency by changing resolution.
- assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- 150, 50, /* frequencyResolutionHz= */ 10, null))
- .build().getFrequencyRangeHz());
+ assertTrue(new VibratorInfo.FrequencyProfile(
+ 150, 50, /* frequencyResolutionHz= */ 10, TEST_AMPLITUDE_MAP).isEmpty());
}
@Test
- public void testGetFrequencyRangeHz_resultRangeDerivedFromHalMapping() {
- VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
- /* resonantFrequencyHz= */ 150,
- /* minFrequencyHz= */ 50,
- /* frequencyResolutionHz= */ 25,
- new float[]{
- /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f,
- /* 200Hz= */ 0.8f}))
- .build();
+ public void testGetMaxAmplitude_emptyProfileReturnsAlwaysZero() {
+ VibratorInfo.FrequencyProfile profile = EMPTY_FREQUENCY_PROFILE;
+ assertEquals(0f, profile.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(100f), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(200f), TEST_TOLERANCE);
- assertEquals(Range.create(50f, 200f), info.getFrequencyRangeHz());
- }
-
- @Test
- public void testGetMaxAmplitude_emptyMappingReturnsAlwaysZero() {
- VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
- assertEquals(0f, info.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
- assertEquals(0f, info.getMaxAmplitude(100f), TEST_TOLERANCE);
- assertEquals(0f, info.getMaxAmplitude(200f), TEST_TOLERANCE);
-
- info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ profile = new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 150,
/* minFrequencyHz= */ Float.NaN,
/* frequencyResolutionHz= */ Float.NaN,
- null))
- .build();
+ /* maxAmplitudes= */ null);
- assertEquals(0f, info.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
- assertEquals(0f, info.getMaxAmplitude(100f), TEST_TOLERANCE);
- assertEquals(0f, info.getMaxAmplitude(150f), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(100f), TEST_TOLERANCE);
+ assertEquals(0f, profile.getMaxAmplitude(150f), TEST_TOLERANCE);
}
@Test
- public void testGetMaxAmplitude_validMappingReturnsMappedValues() {
- VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ public void testGetMaxAmplitude_validprofileReturnsMappedValues() {
+ VibratorInfo.FrequencyProfile profile = new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 150,
/* minFrequencyHz= */ 50,
/* frequencyResolutionHz= */ 25,
- new float[]{
+ /* maxAmplitudes= */ new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f,
- /* 200Hz= */ 0.8f}))
- .build();
+ /* 200Hz= */ 0.8f});
- assertEquals(1f, info.getMaxAmplitude(150f), TEST_TOLERANCE);
- assertEquals(0.9f, info.getMaxAmplitude(175f), TEST_TOLERANCE);
- assertEquals(0.8f, info.getMaxAmplitude(125f), TEST_TOLERANCE);
- assertEquals(0.8f, info.getMaxAmplitude(info.getFrequencyRangeHz().getUpper()),
- TEST_TOLERANCE); // 200Hz
- assertEquals(0.1f, info.getMaxAmplitude(info.getFrequencyRangeHz().getLower()),
- TEST_TOLERANCE); // 50Hz
+ assertEquals(1f, profile.getMaxAmplitude(150f), TEST_TOLERANCE);
+ assertEquals(0.9f, profile.getMaxAmplitude(175f), TEST_TOLERANCE);
+ assertEquals(0.8f, profile.getMaxAmplitude(125f), TEST_TOLERANCE);
+ assertEquals(0.8f, profile.getMaxAmplitude(200f), TEST_TOLERANCE);
+ assertEquals(0.1f, profile.getMaxAmplitude(50f), TEST_TOLERANCE);
// 145Hz maps to the max amplitude for 125Hz, which is lower.
- assertEquals(0.8f, info.getMaxAmplitude(145f), TEST_TOLERANCE); // 145Hz
+ assertEquals(0.8f, profile.getMaxAmplitude(145f), TEST_TOLERANCE); // 145Hz
// 185Hz maps to the max amplitude for 200Hz, which is lower.
- assertEquals(0.8f, info.getMaxAmplitude(185f), TEST_TOLERANCE); // 185Hz
+ assertEquals(0.8f, profile.getMaxAmplitude(185f), TEST_TOLERANCE); // 185Hz
}
@Test
@@ -245,7 +224,7 @@ public class VibratorInfoTest {
.setPwlePrimitiveDurationMax(50)
.setPwleSizeMax(20)
.setQFactor(2f)
- .setFrequencyMapping(TEST_FREQUENCY_MAPPING);
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE);
VibratorInfo complete = completeBuilder.build();
assertEquals(complete, complete);
@@ -272,23 +251,21 @@ public class VibratorInfoTest {
.build();
assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
- VibratorInfo completeWithDifferentFrequencyMapping = completeBuilder
- .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+ VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
TEST_RESONANT_FREQUENCY + 20,
TEST_MIN_FREQUENCY + 10,
TEST_FREQUENCY_RESOLUTION + 5,
TEST_AMPLITUDE_MAP))
.build();
- assertNotEquals(complete, completeWithDifferentFrequencyMapping);
+ assertNotEquals(complete, completeWithDifferentFrequencyProfile);
- VibratorInfo completeWithEmptyFrequencyMapping = completeBuilder
- .setFrequencyMapping(EMPTY_FREQUENCY_MAPPING)
+ VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder
+ .setFrequencyProfile(EMPTY_FREQUENCY_PROFILE)
.build();
- assertNotEquals(complete, completeWithEmptyFrequencyMapping);
+ assertNotEquals(complete, completeWithEmptyFrequencyProfile);
- VibratorInfo completeWithUnknownQFactor = completeBuilder
- .setQFactor(Float.NaN)
- .build();
+ VibratorInfo completeWithUnknownQFactor = completeBuilder.setQFactor(Float.NaN).build();
assertNotEquals(complete, completeWithUnknownQFactor);
VibratorInfo completeWithDifferentQFactor = completeBuilder
@@ -316,7 +293,7 @@ public class VibratorInfoTest {
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setQFactor(Float.NaN)
- .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
Parcel parcel = Parcel.obtain();
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 981086d6b1529..7a66befad1a1e 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -61,6 +61,8 @@ public class VibratorTest {
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ private static final float TEST_TOLERANCE = 1e-5f;
+
private Context mContextSpy;
private Vibrator mVibratorSpy;
@@ -76,6 +78,9 @@ public class VibratorTest {
@Test
public void getId_returnsDefaultId() {
assertEquals(-1, mVibratorSpy.getId());
+ assertEquals(-1, new SystemVibrator.NoVibratorInfo().getId());
+ assertEquals(-1, new SystemVibrator.MultiVibratorInfo(new VibratorInfo[] {
+ VibratorInfo.EMPTY_VIBRATOR_INFO, VibratorInfo.EMPTY_VIBRATOR_INFO }).getId());
}
@Test
@@ -90,8 +95,7 @@ public class VibratorTest {
@Test
public void areEffectsSupported_noVibrator_returnsAlwaysNo() {
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
- new VibratorInfo[0]);
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
}
@@ -104,7 +108,7 @@ public class VibratorTest {
VibratorInfo unsupportedVibrator = new VibratorInfo.Builder(/* id= */ 2)
.setSupportedEffects(new int[0])
.build();
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
@@ -116,7 +120,7 @@ public class VibratorTest {
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.build();
VibratorInfo unknownSupportVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{supportedVibrator, unknownSupportVibrator});
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
@@ -130,7 +134,7 @@ public class VibratorTest {
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.build();
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, secondVibrator});
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
@@ -148,8 +152,7 @@ public class VibratorTest {
@Test
public void arePrimitivesSupported_noVibrator_returnsAlwaysFalse() {
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
- new VibratorInfo[0]);
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -160,7 +163,7 @@ public class VibratorTest {
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build();
VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -175,7 +178,7 @@ public class VibratorTest {
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 15)
.build();
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, secondVibrator});
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -192,8 +195,7 @@ public class VibratorTest {
@Test
public void getPrimitivesDurations_noVibrator_returnsAlwaysZero() {
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
- new VibratorInfo[0]);
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -204,7 +206,7 @@ public class VibratorTest {
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build();
VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -219,11 +221,179 @@ public class VibratorTest {
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.build();
- SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, secondVibrator});
assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
+ @Test
+ public void getQFactorAndResonantFrequency_noVibrator_returnsNaN() {
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ }
+
+ @Test
+ public void getQFactorAndResonantFrequency_differentValues_returnsNaN() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setQFactor(1f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+ .build();
+ VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setQFactor(2f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, secondVibrator});
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+
+ // One vibrator with values undefined.
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, thirdVibrator});
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ }
+
+ @Test
+ public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setQFactor(10f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 11, 10, 0.5f, null))
+ .build();
+ VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setQFactor(10f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 11, 5, 1, null))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, secondVibrator});
+
+ assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
+ assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+ }
+
+ @Test
+ public void getFrequencyProfile_noVibrator_returnsEmpty() {
+ VibratorInfo info = new SystemVibrator.NoVibratorInfo();
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, differentResonantFrequency});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+
+ VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
+ new float[] { 0, 1 }))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void getFrequencyProfile_missingValues_returnsEmpty() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, missingResonantFrequency});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+
+ VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
+ new float[] { 0, 1 }))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, missingMinFrequency});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+
+ VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
+ new float[] { 0, 1 }))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+
+ VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator});
+
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
+ VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+ new float[] { 0.5f, 1, 1, 0.5f }))
+ .build();
+ VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 1, 1, 1 }))
+ .build();
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0.8f, 1, 0.8f, 0.5f }))
+ .build();
+ VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
+
+ assertEquals(
+ new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+ info.getFrequencyProfile());
+ }
+
@Test
public void vibrate_withVibrationAttributes_usesGivenAttributes() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
index 8189e74f922ca..160f4f971e2f0 100644
--- a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
+++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
@@ -26,8 +26,8 @@ import android.util.Range;
import java.util.List;
/**
- * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRangeHz()} and
- * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
+ * Adapter that clips frequency values to the ones specified by the
+ * {@link VibratorInfo.FrequencyProfile}.
*
* Devices with no frequency control will collapse all frequencies to the resonant frequency and
* leave amplitudes unchanged.
@@ -69,20 +69,20 @@ final class ClippingAmplitudeAndFrequencyAdapter
}
private float clampFrequency(VibratorInfo info, float frequencyHz) {
- Range
*