diff --git a/core/api/current.txt b/core/api/current.txt index bc446bfe63567..25e765662ebb0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -33045,9 +33045,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); @@ -33373,6 +33377,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 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 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 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 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 frequencyRangeIntersection(VibratorInfo[] infos, + float frequencyResolution) { + Range 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 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. */ diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index eba9ff1bb4f8a..23baa5d70c66e 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -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 Q factor 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. + * + *

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 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. * - *

This mapping is defined by the following parameters: + *

This profile is defined by the following parameters: * *

    *
  1. {@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 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 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 CREATOR = - new Creator() { + public static final Creator CREATOR = + new Creator() { @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); } /** diff --git a/core/java/android/os/vibrator/VibratorFrequencyProfile.java b/core/java/android/os/vibrator/VibratorFrequencyProfile.java new file mode 100644 index 0000000000000..23b45aed8eb4f --- /dev/null +++ b/core/java/android/os/vibrator/VibratorFrequencyProfile.java @@ -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. + * + *

    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 frequencyRangeHz = info.getFrequencyRangeHz(); + Range 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 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)); } } diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java index c592a70724074..c943bb283fe7e 100644 --- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java +++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java @@ -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; } } diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java index 5ace3896f387c..86fc642f7230f 100644 --- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java +++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java @@ -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; } } diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index b484796af6e24..f5e6c45c75b87 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -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(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(env->NewGlobalRef(frequencyMappingClass)); - sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "", "(FFF[F)V"); + jclass frequencyProfileClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfile"); + sFrequencyProfileClass = static_cast(env->NewGlobalRef(frequencyProfileClass)); + sFrequencyProfileCtor = GetMethodIDOrDie(env, sFrequencyProfileClass, "", "(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, diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java index 739b3b179de77..5e9e16a0baf88 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java @@ -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(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java index 2ad0e93dd1fb1..e88e9881181c5 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -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; } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java index 22db917367560..a9f37f35e35b9 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java @@ -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(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java index 18ff953446a23..54627c40c7e04 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java @@ -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(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java index cb4982be40c30..f2c1874de3921 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java @@ -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; }); } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index ab86e295e28a0..52975ef8bfe18 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -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