Merge "Expose vibration primitives durations in Vibrator" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
557f9e056e
@@ -32081,6 +32081,7 @@ package android.os {
|
||||
method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
|
||||
method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
|
||||
method public int getId();
|
||||
method @NonNull public int[] getPrimitiveDurations(@NonNull int...);
|
||||
method public abstract boolean hasAmplitudeControl();
|
||||
method public abstract boolean hasVibrator();
|
||||
method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long);
|
||||
|
||||
@@ -551,6 +551,27 @@ public abstract class Vibrator {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the estimated durations of the given primitives.
|
||||
*
|
||||
* The returned array will be the same length as the query array and the value at a given index
|
||||
* will contain the duration in milliseconds of the effect at the same index in the querying
|
||||
* array.
|
||||
*
|
||||
* @param primitiveIds Which primitives to query for.
|
||||
* @return The duration of each primitive, with zeroes for primitives that are not supported.
|
||||
*/
|
||||
@NonNull
|
||||
public int[] getPrimitiveDurations(
|
||||
@NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
|
||||
VibratorInfo info = getInfo();
|
||||
int[] durations = new int[primitiveIds.length];
|
||||
for (int i = 0; i < primitiveIds.length; i++) {
|
||||
durations[i] = info.getPrimitiveDuration(primitiveIds[i]);
|
||||
}
|
||||
return durations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the vibrator off.
|
||||
*/
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.util.Log;
|
||||
import android.util.MathUtils;
|
||||
import android.util.Range;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -51,7 +52,7 @@ public class VibratorInfo implements Parcelable {
|
||||
@Nullable
|
||||
private final SparseBooleanArray mSupportedBraking;
|
||||
@Nullable
|
||||
private final SparseBooleanArray mSupportedPrimitives;
|
||||
private final SparseIntArray mSupportedPrimitives;
|
||||
private final float mQFactor;
|
||||
private final FrequencyMapping mFrequencyMapping;
|
||||
|
||||
@@ -60,19 +61,37 @@ public class VibratorInfo implements Parcelable {
|
||||
mCapabilities = in.readLong();
|
||||
mSupportedEffects = in.readSparseBooleanArray();
|
||||
mSupportedBraking = in.readSparseBooleanArray();
|
||||
mSupportedPrimitives = in.readSparseBooleanArray();
|
||||
mSupportedPrimitives = in.readSparseIntArray();
|
||||
mQFactor = in.readFloat();
|
||||
mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader());
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @param id The vibrator id.
|
||||
* @param capabilities All capability flags of the vibrator, defined in IVibrator.CAP_*.
|
||||
* @param supportedEffects All supported predefined effects, enum values from {@link
|
||||
* android.hardware.vibrator.Effect}.
|
||||
* @param supportedBraking All supported braking types, enum values from {@link Braking}.
|
||||
* @param supportedPrimitives All supported primitive effects, enum values from {@link
|
||||
* android.hardware.vibrator.CompositePrimitive}.
|
||||
* @param primitiveDurations A mapping of primitive durations, where indexes are enum values
|
||||
* from {@link android.hardware.vibrator.CompositePrimitive} and the
|
||||
* values are estimated durations in milliseconds.
|
||||
* @param qFactor The vibrator quality factor.
|
||||
* @param frequencyMapping The description of the vibrator supported frequencies and max
|
||||
* amplitude mappings.
|
||||
* @hide
|
||||
*/
|
||||
public VibratorInfo(int id, long capabilities, int[] supportedEffects, int[] supportedBraking,
|
||||
int[] supportedPrimitives, float qFactor, @NonNull FrequencyMapping frequencyMapping) {
|
||||
int[] supportedPrimitives, int[] primitiveDurations, float qFactor,
|
||||
@NonNull FrequencyMapping frequencyMapping) {
|
||||
mId = id;
|
||||
mCapabilities = capabilities;
|
||||
mSupportedEffects = toSparseBooleanArray(supportedEffects);
|
||||
mSupportedBraking = toSparseBooleanArray(supportedBraking);
|
||||
mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives);
|
||||
mSupportedPrimitives = toSparseIntArray(supportedPrimitives, primitiveDurations);
|
||||
mQFactor = qFactor;
|
||||
mFrequencyMapping = frequencyMapping;
|
||||
}
|
||||
@@ -100,7 +119,7 @@ public class VibratorInfo implements Parcelable {
|
||||
dest.writeLong(mCapabilities);
|
||||
dest.writeSparseBooleanArray(mSupportedEffects);
|
||||
dest.writeSparseBooleanArray(mSupportedBraking);
|
||||
dest.writeSparseBooleanArray(mSupportedPrimitives);
|
||||
dest.writeSparseIntArray(mSupportedPrimitives);
|
||||
dest.writeFloat(mQFactor);
|
||||
dest.writeParcelable(mFrequencyMapping, flags);
|
||||
}
|
||||
@@ -119,18 +138,41 @@ public class VibratorInfo implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
VibratorInfo that = (VibratorInfo) o;
|
||||
if (mSupportedPrimitives == null || that.mSupportedPrimitives == null) {
|
||||
if (mSupportedPrimitives != that.mSupportedPrimitives) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (mSupportedPrimitives.size() != that.mSupportedPrimitives.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
|
||||
if (mSupportedPrimitives.keyAt(i) != that.mSupportedPrimitives.keyAt(i)) {
|
||||
return false;
|
||||
}
|
||||
if (mSupportedPrimitives.valueAt(i) != that.mSupportedPrimitives.valueAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mId == that.mId && mCapabilities == that.mCapabilities
|
||||
&& Objects.equals(mSupportedEffects, that.mSupportedEffects)
|
||||
&& Objects.equals(mSupportedBraking, that.mSupportedBraking)
|
||||
&& Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives)
|
||||
&& Objects.equals(mQFactor, that.mQFactor)
|
||||
&& Objects.equals(mFrequencyMapping, that.mFrequencyMapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
|
||||
mSupportedPrimitives, mQFactor, mFrequencyMapping);
|
||||
int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
|
||||
mQFactor, mFrequencyMapping);
|
||||
if (mSupportedPrimitives != null) {
|
||||
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
|
||||
hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
|
||||
hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
|
||||
}
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,7 +248,19 @@ public class VibratorInfo implements Parcelable {
|
||||
public boolean isPrimitiveSupported(
|
||||
@VibrationEffect.Composition.PrimitiveType int primitiveId) {
|
||||
return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null
|
||||
&& mSupportedPrimitives.get(primitiveId);
|
||||
&& (mSupportedPrimitives.indexOfKey(primitiveId) >= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the estimated duration of given primitive.
|
||||
*
|
||||
* @param primitiveId Which primitives to query for.
|
||||
* @return The duration in milliseconds estimated for the primitive, or zero if primitive not
|
||||
* supported.
|
||||
*/
|
||||
public int getPrimitiveDuration(
|
||||
@VibrationEffect.Composition.PrimitiveType int primitiveId) {
|
||||
return mSupportedPrimitives.get(primitiveId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,14 +418,37 @@ public class VibratorInfo implements Parcelable {
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SparseBooleanArray} from given {@code supportedKeys} where each key is mapped
|
||||
* to {@code true}.
|
||||
*/
|
||||
@Nullable
|
||||
private static SparseBooleanArray toSparseBooleanArray(int[] values) {
|
||||
if (values == null) {
|
||||
private static SparseBooleanArray toSparseBooleanArray(int[] supportedKeys) {
|
||||
if (supportedKeys == null) {
|
||||
return null;
|
||||
}
|
||||
SparseBooleanArray array = new SparseBooleanArray();
|
||||
for (int value : values) {
|
||||
array.put(value, true);
|
||||
for (int key : supportedKeys) {
|
||||
array.put(key, true);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SparseIntArray} from given {@code supportedKeys} where each key is mapped
|
||||
* to the value indexed by it.
|
||||
*
|
||||
* <p>If {@code values} is null or does not contain a given key as a index, then zero is stored
|
||||
* to the sparse array so it can still be used to query the supported keys.
|
||||
*/
|
||||
@Nullable
|
||||
private static SparseIntArray toSparseIntArray(int[] supportedKeys, int[] values) {
|
||||
if (supportedKeys == null) {
|
||||
return null;
|
||||
}
|
||||
SparseIntArray array = new SparseIntArray();
|
||||
for (int key : supportedKeys) {
|
||||
array.put(key, (values == null || key >= values.length) ? 0 : values[key]);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
@@ -419,7 +496,20 @@ public class VibratorInfo implements Parcelable {
|
||||
in.createFloatArray());
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @param minFrequencyHz Minimum supported frequency, in hertz.
|
||||
* @param resonantFrequencyHz The vibrator resonant frequency, in hertz.
|
||||
* @param frequencyResolutionHz The frequency resolution, in hertz, used by the max
|
||||
* amplitudes mapping.
|
||||
* @param suggestedSafeRangeHz The suggested range, in hertz, for the safe relative
|
||||
* frequency range represented by [-1, 1].
|
||||
* @param maxAmplitudes The max amplitude supported by each supported frequency,
|
||||
* starting at minimum frequency with jumps of frequency
|
||||
* resolution.
|
||||
* @hide
|
||||
*/
|
||||
public FrequencyMapping(float minFrequencyHz, float resonantFrequencyHz,
|
||||
float frequencyResolutionHz, float suggestedSafeRangeHz, float[] maxAmplitudes) {
|
||||
mMinFrequencyHz = minFrequencyHz;
|
||||
@@ -547,8 +637,10 @@ public class VibratorInfo implements Parcelable {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz, mFrequencyResolutionHz,
|
||||
mSuggestedSafeRangeHz, mMaxAmplitudes);
|
||||
int hashCode = Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz,
|
||||
mFrequencyResolutionHz, mSuggestedSafeRangeHz);
|
||||
hashCode = 31 * hashCode + Arrays.hashCode(mMaxAmplitudes);
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -587,6 +679,7 @@ public class VibratorInfo implements Parcelable {
|
||||
private int[] mSupportedEffects = null;
|
||||
private int[] mSupportedBraking = null;
|
||||
private int[] mSupportedPrimitives = null;
|
||||
private int[] mPrimitiveDurations = new int[0];
|
||||
private float mQFactor = Float.NaN;
|
||||
private FrequencyMapping mFrequencyMapping =
|
||||
new FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null);
|
||||
@@ -627,6 +720,16 @@ public class VibratorInfo implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Configure the duration of a {@link android.hardware.vibrator.CompositePrimitive}. */
|
||||
@NonNull
|
||||
public Builder setPrimitiveDuration(int primitiveId, int duration) {
|
||||
if (mPrimitiveDurations.length <= primitiveId) {
|
||||
mPrimitiveDurations = Arrays.copyOf(mPrimitiveDurations, primitiveId + 1);
|
||||
}
|
||||
mPrimitiveDurations[primitiveId] = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Configure the vibrator quality factor. */
|
||||
@NonNull
|
||||
public Builder setQFactor(float qFactor) {
|
||||
@@ -645,7 +748,7 @@ public class VibratorInfo implements Parcelable {
|
||||
@NonNull
|
||||
public VibratorInfo build() {
|
||||
return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
|
||||
mSupportedPrimitives, mQFactor, mFrequencyMapping);
|
||||
mSupportedPrimitives, mPrimitiveDurations, mQFactor, mFrequencyMapping);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,17 @@ public class VibratorInfoTest {
|
||||
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPrimitiveDuration() {
|
||||
VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
|
||||
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
|
||||
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
|
||||
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
|
||||
.build();
|
||||
assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
|
||||
assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_TICK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDefaultBraking_returnsFirstSupportedBraking() {
|
||||
assertEquals(Braking.NONE, new VibratorInfo.Builder(
|
||||
@@ -251,12 +262,14 @@ public class VibratorInfoTest {
|
||||
.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
|
||||
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
|
||||
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
|
||||
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
|
||||
.setQFactor(2f)
|
||||
.setFrequencyMapping(TEST_FREQUENCY_MAPPING);
|
||||
VibratorInfo complete = completeBuilder.build();
|
||||
|
||||
assertEquals(complete, complete);
|
||||
assertEquals(complete, completeBuilder.build());
|
||||
assertEquals(complete.hashCode(), completeBuilder.build().hashCode());
|
||||
|
||||
VibratorInfo completeWithComposeControl = completeBuilder
|
||||
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
|
||||
@@ -279,6 +292,11 @@ public class VibratorInfoTest {
|
||||
.build();
|
||||
assertNotEquals(complete, completeWithUnknownPrimitives);
|
||||
|
||||
VibratorInfo completeWithDifferentPrimitiveDuration = completeBuilder
|
||||
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
|
||||
.build();
|
||||
assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
|
||||
|
||||
VibratorInfo completeWithDifferentFrequencyMapping = completeBuilder
|
||||
.setFrequencyMapping(new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY + 10,
|
||||
TEST_RESONANT_FREQUENCY + 20, TEST_FREQUENCY_RESOLUTION + 5,
|
||||
@@ -314,7 +332,8 @@ public class VibratorInfoTest {
|
||||
VibratorInfo original = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
|
||||
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
|
||||
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
|
||||
.setSupportedPrimitives(null)
|
||||
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
|
||||
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
|
||||
.setQFactor(Float.NaN)
|
||||
.setFrequencyMapping(TEST_FREQUENCY_MAPPING)
|
||||
.build();
|
||||
@@ -324,5 +343,6 @@ public class VibratorInfoTest {
|
||||
parcel.setDataPosition(0);
|
||||
VibratorInfo restored = VibratorInfo.CREATOR.createFromParcel(parcel);
|
||||
assertEquals(original, restored);
|
||||
assertEquals(original.hashCode(), restored.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,16 @@ public class VibratorTest {
|
||||
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE}).length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPrimitivesDurations_returnsArrayOfSameSize() {
|
||||
assertEquals(0, mVibratorSpy.getPrimitiveDurations(new int[0]).length);
|
||||
assertEquals(1, mVibratorSpy.getPrimitiveDurations(
|
||||
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}).length);
|
||||
assertEquals(2, mVibratorSpy.getPrimitiveDurations(
|
||||
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE}).length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
|
||||
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
||||
|
||||
@@ -370,6 +370,7 @@ static jobject vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
|
||||
jintArray supportedEffects = nullptr;
|
||||
jintArray supportedBraking = nullptr;
|
||||
jintArray supportedPrimitives = nullptr;
|
||||
jintArray primitiveDurations = nullptr;
|
||||
jfloatArray maxAmplitudes = nullptr;
|
||||
|
||||
if (info.supportedEffects.isOk()) {
|
||||
@@ -390,6 +391,15 @@ static jobject vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
|
||||
env->SetIntArrayRegion(supportedPrimitives, 0, primitives.size(),
|
||||
reinterpret_cast<jint*>(primitives.data()));
|
||||
}
|
||||
if (info.primitiveDurations.isOk()) {
|
||||
std::vector<int32_t> durations;
|
||||
for (auto duration : info.primitiveDurations.value()) {
|
||||
durations.push_back(duration.count());
|
||||
}
|
||||
primitiveDurations = env->NewIntArray(durations.size());
|
||||
env->SetIntArrayRegion(primitiveDurations, 0, durations.size(),
|
||||
reinterpret_cast<jint*>(durations.data()));
|
||||
}
|
||||
if (info.maxAmplitudes.isOk()) {
|
||||
std::vector<float> amplitudes = info.maxAmplitudes.value();
|
||||
maxAmplitudes = env->NewFloatArray(amplitudes.size());
|
||||
@@ -403,7 +413,7 @@ static jobject vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
|
||||
|
||||
return env->NewObject(sVibratorInfoClass, sVibratorInfoCtor, wrapper->getVibratorId(),
|
||||
capabilities, supportedEffects, supportedBraking, supportedPrimitives,
|
||||
qFactor, frequencyMapping);
|
||||
primitiveDurations, qFactor, frequencyMapping);
|
||||
}
|
||||
|
||||
static const JNINativeMethod method_table[] = {
|
||||
@@ -450,9 +460,10 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
|
||||
sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "<init>", "(FFFF[F)V");
|
||||
|
||||
jclass vibratorInfoClass = FindClassOrDie(env, "android/os/VibratorInfo");
|
||||
sVibratorInfoClass = static_cast<jclass>(env->NewGlobalRef(vibratorInfoClass));
|
||||
sVibratorInfoCtor = GetMethodIDOrDie(env, sVibratorInfoClass, "<init>",
|
||||
"(IJ[I[I[IFLandroid/os/VibratorInfo$FrequencyMapping;)V");
|
||||
sVibratorInfoClass = (jclass)env->NewGlobalRef(vibratorInfoClass);
|
||||
sVibratorInfoCtor =
|
||||
GetMethodIDOrDie(env, sVibratorInfoClass, "<init>",
|
||||
"(IJ[I[I[I[IFLandroid/os/VibratorInfo$FrequencyMapping;)V");
|
||||
|
||||
return jniRegisterNativeMethods(env,
|
||||
"com/android/server/vibrator/VibratorController$NativeWrapper",
|
||||
|
||||
@@ -156,7 +156,7 @@ final class FakeVibratorControllerProvider {
|
||||
mMinFrequency, mResonantFrequency, mFrequencyResolution,
|
||||
suggestedFrequencyRange, mMaxAmplitudes);
|
||||
return new VibratorInfo(vibratorId, mCapabilities, mSupportedEffects, mSupportedBraking,
|
||||
mSupportedPrimitives, mQFactor, frequencyMapping);
|
||||
mSupportedPrimitives, null, mQFactor, frequencyMapping);
|
||||
}
|
||||
|
||||
private void applyLatency() {
|
||||
|
||||
Reference in New Issue
Block a user