Create VibratorFrequencyProfile in Vibrator API.

Create public class to represent the vibrator response for the
supported frequency range to the Vibrator public API, with basic
implementation that extracts these values from FrequencyMapping.

Integrate this data with the result of Vibrator.hasFrequencyControl
method, which also checks the HAL capabilities.

Also make public the methods to provide resonant frequency and Q-factor
of the device. Update the SystemVibrator to handle multi-vibrator
scenario and return values when all vibrators have the same
configurations. This includes returning the intersection of all vibrator
max amplitude measurements in the new profile object.

Add validation to HAL values and CTS to cover expected ranges based on
the device support for frequency control.

Bug: 203785430
Test: android.os.cts.VibratorTest & VibratorInfoTest
Change-Id: I0b7cf599ae6039355a0a198f4bdaafb85fb7422c
This commit is contained in:
Lais Andrade
2022-01-10 22:20:42 +00:00
parent 6ae7244803
commit cb3718bfd4
17 changed files with 833 additions and 282 deletions

View File

@@ -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 {

View File

@@ -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}.
*
* <p>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}.
*
* <p>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<VibratorInfo, Integer> propertyGetter) {
int limit = 0; // Limit 0 means unlimited
for (VibratorInfo info : infos) {
int vibratorLimit = propertyGetter.apply(info);
if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
// This vibrator is limited and intersection is unlimited or has a larger limit:
// use smaller limit here for the intersection.
limit = vibratorLimit;
}
}
return limit;
}
private static float floatPropertyIntersection(VibratorInfo[] infos,
Function<VibratorInfo, Float> propertyGetter) {
float property = propertyGetter.apply(infos[0]);
if (Float.isNaN(property)) {
// If one vibrator is undefined then the intersection is undefined.
return Float.NaN;
}
for (int i = 1; i < infos.length; i++) {
if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
// If one vibrator has a different value then the intersection is undefined.
return Float.NaN;
}
}
return property;
}
@NonNull
private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
float freqResolution = floatPropertyIntersection(infos,
info -> info.getFrequencyProfile().getFrequencyResolutionHz());
float resonantFreq = floatPropertyIntersection(infos,
VibratorInfo::getResonantFrequencyHz);
Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
if ((freqRange == null) || Float.isNaN(freqResolution)) {
return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
}
int amplitudeCount =
Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
float[] maxAmplitudes = new float[amplitudeCount];
// Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
// will fail if the loop below is broken and do not replace filled values with actual
// vibrator measurements.
Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
for (VibratorInfo info : infos) {
Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
int vibratorStartIdx = Math.round(
(freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
+ " profiles: attempted to fetch from vibrator "
+ info.getId() + " max amplitude with bad index " + vibratorStartIdx);
return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
}
for (int i = 0; i < maxAmplitudes.length; i++) {
maxAmplitudes[i] = Math.min(maxAmplitudes[i],
vibratorMaxAmplitudes[vibratorStartIdx + i]);
}
}
return new FrequencyProfile(resonantFreq, freqRange.getLower(),
freqResolution, maxAmplitudes);
}
@Nullable
private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
float frequencyResolution) {
Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
if (firstRange == null) {
// If one vibrator is undefined then the intersection is undefined.
return null;
}
float intersectionLower = firstRange.getLower();
float intersectionUpper = firstRange.getUpper();
// Generate the intersection of all vibrator supported ranges, making sure that both
// min supported frequencies are aligned w.r.t. the frequency resolution.
for (int i = 1; i < infos.length; i++) {
Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
if (vibratorRange == null) {
// If one vibrator is undefined then the intersection is undefined.
return null;
}
if ((vibratorRange.getLower() >= intersectionUpper)
|| (vibratorRange.getUpper() <= intersectionLower)) {
// If the range and intersection are disjoint then the intersection is undefined
return null;
}
float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
if ((frequencyDelta % frequencyResolution) > EPSILON) {
// If the intersection is not aligned with one vibrator then it's undefined
return null;
}
intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
}
if ((intersectionUpper - intersectionLower) < frequencyResolution) {
// If the intersection is empty then it's undefined.
return null;
}
return Range.create(intersectionLower, intersectionUpper);
}
}
/** Listener for all vibrators state change. */

View File

@@ -31,6 +31,7 @@ import android.content.res.Resources;
import android.hardware.vibrator.IVibrator;
import android.media.AudioAttributes;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibratorFrequencyProfile;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -208,8 +209,8 @@ public abstract class Vibrator {
/**
* Check whether the vibrator has independent frequency control.
*
* @return True if the hardware can control the frequency of the vibrations, otherwise false.
* @hide
* @return True if the hardware can control the frequency of the vibrations independently of
* the vibration amplitude, false otherwise.
*/
public boolean hasFrequencyControl() {
// We currently can only control frequency of the vibration using the compose PWLE method.
@@ -229,27 +230,47 @@ public abstract class Vibrator {
}
/**
* Gets the resonant frequency of the vibrator.
* Gets the resonant frequency of the vibrator, if applicable.
*
* @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.
* @hide
* @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
* applicable, or if this vibrator is a composite of multiple physical devices with different
* frequencies.
*/
public float getResonantFrequency() {
return getInfo().getResonantFrequency();
return getInfo().getResonantFrequencyHz();
}
/**
* Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
*
* @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
* this vibrator is a composite of multiple physical devices.
* @hide
* @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
* applicable, or if this vibrator is a composite of multiple physical devices with different
* Q factors.
*/
public float getQFactor() {
return getInfo().getQFactor();
}
/**
* Gets the profile that describes the vibrator output across the supported frequency range.
*
* <p>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.
*

View File

@@ -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
* <p>If the devices does not have frequency control then the profile should be empty.
*/
@Nullable
public Range<Float> getFrequencyRangeHz() {
return mFrequencyMapping.mFrequencyRangeHz;
}
/**
* Return the maximum amplitude the vibrator can play at given frequency.
*
* @param frequencyHz The frequency, in hertz, for query.
* @return a value in [0,1] representing the maximum amplitude the device can play at given
* frequency. Devices without frequency control will return 0 to any input. Devices with
* frequency control will return the supported value, for input in
* {@link #getFrequencyRangeHz()}, and 0 for any other input.
* @hide
*/
@FloatRange(from = 0, to = 1)
public float getMaxAmplitude(float frequencyHz) {
return mFrequencyMapping.getMaxAmplitude(frequencyHz);
@NonNull
public FrequencyProfile getFrequencyProfile() {
return mFrequencyProfile;
}
protected long getCapabilities() {
@@ -452,7 +477,7 @@ public class VibratorInfo implements Parcelable {
* Describes the maximum relative output acceleration that can be achieved for each supported
* frequency in a specific vibrator.
*
* <p>This mapping is defined by the following parameters:
* <p>This profile is defined by the following parameters:
*
* <ol>
* <li>{@code minFrequencyHz}, {@code resonantFrequencyHz} and {@code frequencyResolutionHz}
@@ -466,7 +491,7 @@ public class VibratorInfo implements Parcelable {
*
* @hide
*/
public static final class FrequencyMapping implements Parcelable {
public static final class FrequencyProfile implements Parcelable {
@Nullable
private final Range<Float> mFrequencyRangeHz;
private final float mMinFrequencyHz;
@@ -474,7 +499,7 @@ public class VibratorInfo implements Parcelable {
private final float mFrequencyResolutionHz;
private final float[] mMaxAmplitudes;
FrequencyMapping(Parcel in) {
FrequencyProfile(Parcel in) {
this(in.readFloat(), in.readFloat(), in.readFloat(), in.createFloatArray());
}
@@ -484,13 +509,13 @@ public class VibratorInfo implements Parcelable {
* @param resonantFrequencyHz The vibrator resonant frequency, in hertz.
* @param minFrequencyHz Minimum supported frequency, in hertz.
* @param frequencyResolutionHz The frequency resolution, in hertz, used by the max
* amplitudes mapping.
* amplitude measurements.
* @param maxAmplitudes The max amplitude supported by each supported frequency,
* starting at minimum frequency with jumps of frequency
* resolution.
* @hide
*/
public FrequencyMapping(float resonantFrequencyHz, float minFrequencyHz,
public FrequencyProfile(float resonantFrequencyHz, float minFrequencyHz,
float frequencyResolutionHz, float[] maxAmplitudes) {
mMinFrequencyHz = minFrequencyHz;
mResonantFrequencyHz = resonantFrequencyHz;
@@ -500,18 +525,25 @@ public class VibratorInfo implements Parcelable {
System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length);
}
// If any required field is undefined then leave this mapping empty.
// If any required field is undefined or has a bad value then this profile is invalid.
boolean isValid = !Float.isNaN(resonantFrequencyHz)
&& (resonantFrequencyHz > 0)
&& !Float.isNaN(minFrequencyHz)
&& (minFrequencyHz > 0)
&& !Float.isNaN(frequencyResolutionHz)
&& (frequencyResolutionHz > 0)
&& (mMaxAmplitudes.length > 0);
// If any max amplitude is outside the allowed range then this profile is invalid.
for (int i = 0; i < mMaxAmplitudes.length; i++) {
isValid &= (mMaxAmplitudes[i] >= 0) && (mMaxAmplitudes[i] <= 1);
}
float maxFrequencyHz = isValid
? minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1)
: Float.NaN;
// If the non-empty mapping does not have min < resonant < max frequency respected
// then leave this mapping empty.
// If the constraint min < resonant < max is not met then it is invalid.
isValid &= !Float.isNaN(maxFrequencyHz)
&& (resonantFrequencyHz >= minFrequencyHz)
&& (resonantFrequencyHz <= maxFrequencyHz)
@@ -520,14 +552,17 @@ public class VibratorInfo implements Parcelable {
mFrequencyRangeHz = isValid ? Range.create(minFrequencyHz, maxFrequencyHz) : null;
}
/**
* Returns true if this frequency mapping is empty, i.e. the only supported is the resonant
* frequency.
*/
/** Returns true if the supported frequency range is empty. */
public boolean isEmpty() {
return mFrequencyRangeHz == null;
}
/** Returns the supported frequency range, in hertz. */
@Nullable
public Range<Float> getFrequencyRangeHz() {
return mFrequencyRangeHz;
}
/**
* Returns the maximum relative amplitude the vibrator can reach while playing at the
* given frequency.
@@ -535,7 +570,7 @@ public class VibratorInfo implements Parcelable {
* @param frequencyHz frequency, in hertz, for query.
* @return A value in [0,1] representing the max relative amplitude supported at the given
* frequency. This will return 0 if the frequency is outside the supported range, or if the
* mapping is empty.
* supported frequency range is empty.
*/
public float getMaxAmplitude(float frequencyHz) {
if (isEmpty() || Float.isNaN(frequencyHz)) {
@@ -555,6 +590,17 @@ public class VibratorInfo implements Parcelable {
return mMaxAmplitudes[floorIndex];
}
/** Returns the raw list of maximum relative output accelerations from the vibrator. */
@NonNull
public float[] getMaxAmplitudes() {
return Arrays.copyOf(mMaxAmplitudes, mMaxAmplitudes.length);
}
/** Returns the raw frequency resolution used for max amplitude measurements, in hertz. */
public float getFrequencyResolutionHz() {
return mFrequencyResolutionHz;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(mResonantFrequencyHz);
@@ -573,10 +619,10 @@ public class VibratorInfo implements Parcelable {
if (this == o) {
return true;
}
if (!(o instanceof FrequencyMapping)) {
if (!(o instanceof FrequencyProfile)) {
return false;
}
FrequencyMapping that = (FrequencyMapping) o;
FrequencyProfile that = (FrequencyProfile) o;
return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0
&& Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
&& Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0
@@ -593,7 +639,7 @@ public class VibratorInfo implements Parcelable {
@Override
public String toString() {
return "FrequencyMapping{"
return "FrequencyProfile{"
+ "mFrequencyRange=" + mFrequencyRangeHz
+ ", mMinFrequency=" + mMinFrequencyHz
+ ", mResonantFrequency=" + mResonantFrequencyHz
@@ -603,16 +649,16 @@ public class VibratorInfo implements Parcelable {
}
@NonNull
public static final Creator<FrequencyMapping> CREATOR =
new Creator<FrequencyMapping>() {
public static final Creator<FrequencyProfile> CREATOR =
new Creator<FrequencyProfile>() {
@Override
public FrequencyMapping createFromParcel(Parcel in) {
return new FrequencyMapping(in);
public FrequencyProfile createFromParcel(Parcel in) {
return new FrequencyProfile(in);
}
@Override
public FrequencyMapping[] newArray(int size) {
return new FrequencyMapping[size];
public FrequencyProfile[] newArray(int size) {
return new FrequencyProfile[size];
}
};
}
@@ -629,8 +675,8 @@ public class VibratorInfo implements Parcelable {
private int mPwlePrimitiveDurationMax;
private int mPwleSizeMax;
private float mQFactor = Float.NaN;
private FrequencyMapping mFrequencyMapping =
new FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null);
private FrequencyProfile mFrequencyProfile =
new FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
/** A builder class for a {@link VibratorInfo}. */
public Builder(int id) {
@@ -702,8 +748,8 @@ public class VibratorInfo implements Parcelable {
/** Configure the vibrator frequency information like resonant frequency and bandwidth. */
@NonNull
public Builder setFrequencyMapping(FrequencyMapping frequencyMapping) {
mFrequencyMapping = frequencyMapping;
public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) {
mFrequencyProfile = frequencyProfile;
return this;
}
@@ -712,7 +758,7 @@ public class VibratorInfo implements Parcelable {
public VibratorInfo build() {
return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax,
mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyMapping);
mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfile);
}
/**

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2022 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.FloatRange;
import android.annotation.NonNull;
import android.os.VibratorInfo;
import com.android.internal.util.Preconditions;
/**
* Describes the output of a {@link android.os.Vibrator} for different vibration frequencies.
*
* <p>The profile contains the minimum and maximum supported vibration frequencies, if the device
* supports independent frequency control.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>The frequency of a measurement is determined as:
*
* {@code getMinFrequency() + measurementIndex * getMaxAmplitudeMeasurementInterval()}
*
* <p>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();
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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}.
*
* <p>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<Float> frequencyRangeHz = info.getFrequencyRangeHz();
Range<Float> frequencyRangeHz = info.getFrequencyProfile().getFrequencyRangeHz();
if (frequencyHz == 0 || frequencyRangeHz == null) {
return info.getResonantFrequency();
return info.getResonantFrequencyHz();
}
return frequencyRangeHz.clamp(frequencyHz);
}
private float clampAmplitude(VibratorInfo info, float frequencyHz, float amplitude) {
Range<Float> frequencyRangeHz = info.getFrequencyRangeHz();
if (frequencyRangeHz == null) {
// No frequency range was specified, leave amplitude unchanged, the frequency will be
// clamped to the device's resonant frequency.
VibratorInfo.FrequencyProfile mapping = info.getFrequencyProfile();
if (mapping.isEmpty()) {
// No frequency mapping was specified so leave amplitude unchanged.
// The frequency will be clamped to the device's resonant frequency.
return amplitude;
}
return MathUtils.min(amplitude, info.getMaxAmplitude(frequencyHz));
return MathUtils.min(amplitude, mapping.getMaxAmplitude(frequencyHz));
}
}

View File

@@ -94,6 +94,6 @@ final class RampToStepAdapter implements VibrationEffectAdapters.SegmentsAdapter
}
private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
return frequencyHz == 0 ? info.getResonantFrequency() : frequencyHz;
return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
}
}

View File

@@ -148,6 +148,6 @@ final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter
}
private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
return frequencyHz == 0 ? info.getResonantFrequency() : frequencyHz;
return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
}
}

View File

@@ -39,8 +39,8 @@ namespace android {
static JavaVM* sJvm = nullptr;
static jmethodID sMethodIdOnComplete;
static jclass sFrequencyMappingClass;
static jmethodID sFrequencyMappingCtor;
static jclass sFrequencyProfileClass;
static jmethodID sFrequencyProfileCtor;
static struct {
jmethodID setCapabilities;
jmethodID setSupportedEffects;
@@ -51,7 +51,7 @@ static struct {
jmethodID setPrimitiveDelayMax;
jmethodID setCompositionSizeMax;
jmethodID setQFactor;
jmethodID setFrequencyMapping;
jmethodID setFrequencyProfile;
} sVibratorInfoBuilderClassInfo;
static struct {
jfieldID id;
@@ -437,11 +437,11 @@ static jboolean vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
env->SetFloatArrayRegion(maxAmplitudes, 0, amplitudes.size(),
reinterpret_cast<jfloat*>(amplitudes.data()));
}
jobject frequencyMapping =
env->NewObject(sFrequencyMappingClass, sFrequencyMappingCtor, resonantFrequency,
jobject frequencyProfile =
env->NewObject(sFrequencyProfileClass, sFrequencyProfileCtor, resonantFrequency,
minFrequency, frequencyResolution, maxAmplitudes);
env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyMapping,
frequencyMapping);
env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyProfile,
frequencyProfile);
return info.isFailedLogged("vibratorGetInfo") ? JNI_FALSE : JNI_TRUE;
}
@@ -485,9 +485,9 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
sRampClassInfo.endFrequencyHz = GetFieldIDOrDie(env, rampClass, "mEndFrequencyHz", "F");
sRampClassInfo.duration = GetFieldIDOrDie(env, rampClass, "mDuration", "I");
jclass frequencyMappingClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyMapping");
sFrequencyMappingClass = static_cast<jclass>(env->NewGlobalRef(frequencyMappingClass));
sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "<init>", "(FFF[F)V");
jclass frequencyProfileClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfile");
sFrequencyProfileClass = static_cast<jclass>(env->NewGlobalRef(frequencyProfileClass));
sFrequencyProfileCtor = GetMethodIDOrDie(env, sFrequencyProfileClass, "<init>", "(FFF[F)V");
jclass vibratorInfoBuilderClass = FindClassOrDie(env, "android/os/VibratorInfo$Builder");
sVibratorInfoBuilderClassInfo.setCapabilities =
@@ -517,9 +517,9 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
sVibratorInfoBuilderClassInfo.setQFactor =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setQFactor",
"(F)Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setFrequencyMapping =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyMapping",
"(Landroid/os/VibratorInfo$FrequencyMapping;)"
sVibratorInfoBuilderClassInfo.setFrequencyProfile =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfile",
"(Landroid/os/VibratorInfo$FrequencyProfile;)"
"Landroid/os/VibratorInfo$Builder;");
return jniRegisterNativeMethods(env,

View File

@@ -53,10 +53,10 @@ public class DeviceVibrationEffectAdapterTest {
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);
private DeviceVibrationEffectAdapter mAdapter;
@@ -79,8 +79,8 @@ public class DeviceVibrationEffectAdapterTest {
new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
/* repeatIndex= */ -1);
assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING)));
assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_PROFILE)));
assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_PROFILE)));
}
@Test
@@ -97,7 +97,7 @@ public class DeviceVibrationEffectAdapterTest {
/* repeatIndex= */ 3);
VibrationEffect.Composed adaptedEffect = (VibrationEffect.Composed) mAdapter.apply(effect,
createVibratorInfo(EMPTY_FREQUENCY_MAPPING));
createVibratorInfo(EMPTY_FREQUENCY_PROFILE));
assertTrue(adaptedEffect.getSegments().size() > effect.getSegments().size());
assertTrue(adaptedEffect.getRepeatIndex() >= effect.getRepeatIndex());
@@ -128,7 +128,7 @@ public class DeviceVibrationEffectAdapterTest {
/* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)),
/* repeatIndex= */ 2);
VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(expected, mAdapter.apply(effect, info));
}
@@ -159,7 +159,7 @@ public class DeviceVibrationEffectAdapterTest {
/* duration= */ 20)),
/* repeatIndex= */ 2);
VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING,
VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_PROFILE,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(expected, mAdapter.apply(effect, info));
}
@@ -188,17 +188,17 @@ public class DeviceVibrationEffectAdapterTest {
/* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)),
/* repeatIndex= */ 2);
VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
assertEquals(expected, mAdapter.apply(effect, info));
}
private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping,
private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyProfile frequencyProfile,
int... capabilities) {
int cap = IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0);
return new VibratorInfo.Builder(0)
.setCapabilities(cap)
.setFrequencyMapping(frequencyMapping)
.setFrequencyProfile(frequencyProfile)
.build();
}
}

View File

@@ -170,7 +170,7 @@ final class FakeVibratorControllerProvider {
}
infoBuilder.setCompositionSizeMax(mCompositionSizeMax);
infoBuilder.setQFactor(mQFactor);
infoBuilder.setFrequencyMapping(new VibratorInfo.FrequencyMapping(
infoBuilder.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
mResonantFrequency, mMinFrequency, mFrequencyResolution, mMaxAmplitudes));
return mIsInfoLoadSuccessful;
}

View File

@@ -47,8 +47,8 @@ public class RampToStepAdapterTest {
private static final int TEST_STEP_DURATION = 5;
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 TEST_FREQUENCY_MAPPING =
new VibratorInfo.FrequencyMapping(
private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
/* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
@@ -124,7 +124,7 @@ public class RampToStepAdapterTest {
private static VibratorInfo createVibratorInfo(int... capabilities) {
return new VibratorInfo.Builder(0)
.setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
.setFrequencyMapping(TEST_FREQUENCY_MAPPING)
.setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
}
}

View File

@@ -46,8 +46,8 @@ import java.util.stream.IntStream;
public class StepToRampAdapterTest {
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 TEST_FREQUENCY_MAPPING =
new VibratorInfo.FrequencyMapping(
private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
/* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
@@ -99,7 +99,7 @@ public class StepToRampAdapterTest {
VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
.setPwlePrimitiveDurationMax(10)
.setFrequencyMapping(TEST_FREQUENCY_MAPPING)
.setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
// Update repeat index to skip the ramp splits.
@@ -191,7 +191,7 @@ public class StepToRampAdapterTest {
private static VibratorInfo createVibratorInfo(int... capabilities) {
return new VibratorInfo.Builder(0)
.setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
.setFrequencyMapping(TEST_FREQUENCY_MAPPING)
.setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
}
}

View File

@@ -310,13 +310,13 @@ public class VibratorControllerTest {
}
private void mockVibratorCapabilities(int capabilities) {
VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping(
VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
Float.NaN, Float.NaN, Float.NaN, null);
when(mNativeWrapperMock.getInfo(any(VibratorInfo.Builder.class)))
.then(invocation -> {
((VibratorInfo.Builder) invocation.getArgument(0))
.setCapabilities(capabilities)
.setFrequencyMapping(frequencyMapping);
.setFrequencyProfile(frequencyProfile);
return true;
});
}

View File

@@ -316,7 +316,7 @@ public class VibratorManagerServiceTest {
assertNotNull(info);
assertEquals(1, info.getId());
assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
}
@Test
@@ -341,7 +341,7 @@ public class VibratorManagerServiceTest {
info.isEffectSupported(VibrationEffect.EFFECT_TICK));
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
assertTrue(Float.isNaN(info.getQFactor()));
}
@@ -360,7 +360,7 @@ public class VibratorManagerServiceTest {
VibratorInfo info = createService().getVibratorInfo(1);
assertNotNull(info);
assertEquals(1, info.getId());
assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
}
@Test