Merge "Extract VibratorInfo aggregation logic to a factory" into main
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index bf72b1d..2cda787 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -18,26 +18,19 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.hardware.vibrator.IVibrator;
+import android.os.vibrator.VibratorInfoFactory;
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.
@@ -82,7 +75,7 @@
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();
+ return mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
}
VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
for (int i = 0; i < vibratorIds.length; i++) {
@@ -96,12 +89,7 @@
}
vibratorInfos[i] = vibrator.getInfo();
}
- 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);
+ return mVibratorInfo = VibratorInfoFactory.create(/* id= */ -1, vibratorInfos);
}
}
@@ -275,296 +263,6 @@
}
/**
- * Represents a device with no vibrator as a single {@link VibratorInfo}.
- *
- * @hide
- */
- @VisibleForTesting
- public static class NoVibratorInfo extends VibratorInfo {
- public NoVibratorInfo() {
- // Use empty arrays to indicate no support, while null would indicate support unknown.
- super(/* id= */ -1,
- /* capabilities= */ 0,
- /* supportedEffects= */ new SparseBooleanArray(),
- /* supportedBraking= */ new SparseBooleanArray(),
- /* supportedPrimitives= */ new SparseIntArray(),
- /* primitiveDelayMax= */ 0,
- /* compositionSizeMax= */ 0,
- /* pwlePrimitiveDurationMax= */ 0,
- /* pwleSizeMax= */ 0,
- /* qFactor= */ Float.NaN,
- new FrequencyProfile(/* resonantFrequencyHz= */ Float.NaN,
- /* minFrequencyHz= */ Float.NaN,
- /* frequencyResolutionHz= */ Float.NaN,
- /* maxAmplitudes= */ null));
- }
- }
-
- /**
- * Represents multiple vibrator information as a single {@link VibratorInfo}.
- *
- * <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive
- * support.
- *
- * @hide
- */
- @VisibleForTesting
- public static class MultiVibratorInfo extends VibratorInfo {
- // Epsilon used for float comparison applied in calculations for the merged info.
- private static final float EPSILON = 1e-5f;
-
- public MultiVibratorInfo(VibratorInfo[] vibrators) {
- // Need to use an extra constructor to share the computation in super initialization.
- this(vibrators, frequencyProfileIntersection(vibrators));
- }
-
- private MultiVibratorInfo(VibratorInfo[] vibrators,
- VibratorInfo.FrequencyProfile mergedProfile) {
- super(/* id= */ -1,
- capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
- 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),
- mergedProfile);
- }
-
- private static int capabilitiesIntersection(VibratorInfo[] infos,
- boolean frequencyProfileIsEmpty) {
- int intersection = ~0;
- for (VibratorInfo info : infos) {
- intersection &= info.getCapabilities();
- }
- if (frequencyProfileIsEmpty) {
- // Revoke frequency control if the merged frequency profile ended up empty.
- intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
- }
- return intersection;
- }
-
- @Nullable
- private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) {
- for (VibratorInfo info : infos) {
- if (!info.isBrakingSupportKnown()) {
- // If one vibrator support is unknown, then the intersection is also unknown.
- return null;
- }
- }
-
- SparseBooleanArray intersection = new SparseBooleanArray();
- SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking();
-
- brakingIdLoop:
- for (int i = 0; i < firstVibratorBraking.size(); i++) {
- int brakingId = firstVibratorBraking.keyAt(i);
- if (!firstVibratorBraking.valueAt(i)) {
- // The first vibrator already doesn't support this braking, so skip it.
- continue brakingIdLoop;
- }
-
- for (int j = 1; j < infos.length; j++) {
- if (!infos[j].hasBrakingSupport(brakingId)) {
- // One vibrator doesn't support this braking, so the intersection doesn't.
- continue brakingIdLoop;
- }
- }
-
- intersection.put(brakingId, true);
- }
-
- return intersection;
- }
-
- @Nullable
- private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) {
- for (VibratorInfo info : infos) {
- if (!info.isEffectSupportKnown()) {
- // If one vibrator support is unknown, then the intersection is also unknown.
- return null;
- }
- }
-
- SparseBooleanArray intersection = new SparseBooleanArray();
- SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects();
-
- effectIdLoop:
- for (int i = 0; i < firstVibratorEffects.size(); i++) {
- int effectId = firstVibratorEffects.keyAt(i);
- if (!firstVibratorEffects.valueAt(i)) {
- // The first vibrator already doesn't support this effect, so skip it.
- continue effectIdLoop;
- }
-
- for (int j = 1; j < infos.length; j++) {
- if (infos[j].isEffectSupported(effectId) != VIBRATION_EFFECT_SUPPORT_YES) {
- // One vibrator doesn't support this effect, so the intersection doesn't.
- continue effectIdLoop;
- }
- }
-
- intersection.put(effectId, true);
- }
-
- return intersection;
- }
-
- @NonNull
- private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
- VibratorInfo[] infos) {
- SparseIntArray intersection = new SparseIntArray();
- SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
-
- primitiveIdLoop:
- for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
- int primitiveId = firstVibratorPrimitives.keyAt(i);
- int primitiveDuration = firstVibratorPrimitives.valueAt(i);
- if (primitiveDuration == 0) {
- // The first vibrator already doesn't support this primitive, so skip it.
- continue primitiveIdLoop;
- }
-
- for (int j = 1; j < infos.length; j++) {
- int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
- if (vibratorPrimitiveDuration == 0) {
- // One vibrator doesn't support this primitive, so the intersection doesn't.
- continue primitiveIdLoop;
- } else {
- // The primitive vibration duration is the maximum among all vibrators.
- primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
- }
- }
-
- intersection.put(primitiveId, primitiveDuration);
- }
- return intersection;
- }
-
- private static int integerLimitIntersection(VibratorInfo[] infos,
- Function<VibratorInfo, Integer> propertyGetter) {
- int limit = 0; // Limit 0 means unlimited
- for (VibratorInfo info : infos) {
- int vibratorLimit = propertyGetter.apply(info);
- if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
- // This vibrator is limited and intersection is unlimited or has a larger limit:
- // use smaller limit here for the intersection.
- limit = vibratorLimit;
- }
- }
- return limit;
- }
-
- private static float floatPropertyIntersection(VibratorInfo[] infos,
- Function<VibratorInfo, Float> propertyGetter) {
- float property = propertyGetter.apply(infos[0]);
- if (Float.isNaN(property)) {
- // If one vibrator is undefined then the intersection is undefined.
- return Float.NaN;
- }
- for (int i = 1; i < infos.length; i++) {
- if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
- // If one vibrator has a different value then the intersection is undefined.
- return Float.NaN;
- }
- }
- return property;
- }
-
- @NonNull
- private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
- float freqResolution = floatPropertyIntersection(infos,
- info -> info.getFrequencyProfile().getFrequencyResolutionHz());
- float resonantFreq = floatPropertyIntersection(infos,
- VibratorInfo::getResonantFrequencyHz);
- Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
-
- if ((freqRange == null) || Float.isNaN(freqResolution)) {
- return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
- }
-
- int amplitudeCount =
- Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
- float[] maxAmplitudes = new float[amplitudeCount];
-
- // Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
- // will fail if the loop below is broken and do not replace filled values with actual
- // vibrator measurements.
- Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
-
- for (VibratorInfo info : infos) {
- Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
- float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
- int vibratorStartIdx = Math.round(
- (freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
- int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
-
- if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
- Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
- + " profiles: attempted to fetch from vibrator "
- + info.getId() + " max amplitude with bad index " + vibratorStartIdx);
- return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
- }
-
- for (int i = 0; i < maxAmplitudes.length; i++) {
- maxAmplitudes[i] = Math.min(maxAmplitudes[i],
- vibratorMaxAmplitudes[vibratorStartIdx + i]);
- }
- }
-
- return new FrequencyProfile(resonantFreq, freqRange.getLower(),
- freqResolution, maxAmplitudes);
- }
-
- @Nullable
- private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
- float frequencyResolution) {
- Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
- if (firstRange == null) {
- // If one vibrator is undefined then the intersection is undefined.
- return null;
- }
- float intersectionLower = firstRange.getLower();
- float intersectionUpper = firstRange.getUpper();
-
- // Generate the intersection of all vibrator supported ranges, making sure that both
- // min supported frequencies are aligned w.r.t. the frequency resolution.
-
- for (int i = 1; i < infos.length; i++) {
- Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
- if (vibratorRange == null) {
- // If one vibrator is undefined then the intersection is undefined.
- return null;
- }
-
- if ((vibratorRange.getLower() >= intersectionUpper)
- || (vibratorRange.getUpper() <= intersectionLower)) {
- // If the range and intersection are disjoint then the intersection is undefined
- return null;
- }
-
- float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
- if ((frequencyDelta % frequencyResolution) > EPSILON) {
- // If the intersection is not aligned with one vibrator then it's undefined
- return null;
- }
-
- intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
- intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
- }
-
- if ((intersectionUpper - intersectionLower) < frequencyResolution) {
- // If the intersection is empty then it's undefined.
- return null;
- }
-
- return Range.create(intersectionLower, intersectionUpper);
- }
- }
-
- /**
* Listener for all vibrators state change.
*
* <p>This registers a listener to all vibrators to merge the callbacks into a single state
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 0b7d7c3..4f8c24d 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -156,6 +156,16 @@
return false;
}
VibratorInfo that = (VibratorInfo) o;
+ return mId == that.mId && equalContent(that);
+ }
+
+ /**
+ * Returns {@code true} only if the properties and capabilities of the provided info, except for
+ * the ID, equals to this info. Returns {@code false} otherwise.
+ *
+ * @hide
+ */
+ public boolean equalContent(VibratorInfo that) {
int supportedPrimitivesCount = mSupportedPrimitives.size();
if (supportedPrimitivesCount != that.mSupportedPrimitives.size()) {
return false;
@@ -168,7 +178,7 @@
return false;
}
}
- return mId == that.mId && mCapabilities == that.mCapabilities
+ return mCapabilities == that.mCapabilities
&& mPrimitiveDelayMax == that.mPrimitiveDelayMax
&& mCompositionSizeMax == that.mCompositionSizeMax
&& mPwlePrimitiveDurationMax == that.mPwlePrimitiveDurationMax
@@ -445,7 +455,8 @@
return mFrequencyProfile;
}
- protected long getCapabilities() {
+ /** Returns a single int representing all the capabilities of the vibrator. */
+ public long getCapabilities() {
return mCapabilities;
}
diff --git a/core/java/android/os/vibrator/MultiVibratorInfo.java b/core/java/android/os/vibrator/MultiVibratorInfo.java
new file mode 100644
index 0000000..5f32731
--- /dev/null
+++ b/core/java/android/os/vibrator/MultiVibratorInfo.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2023 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.NonNull;
+import android.annotation.Nullable;
+import android.hardware.vibrator.IVibrator;
+import android.os.Vibrator;
+import android.os.VibratorInfo;
+import android.util.Range;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.util.Arrays;
+import java.util.function.Function;
+
+/**
+ * Represents multiple vibrator information as a single {@link VibratorInfo}.
+ *
+ * <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive
+ * support.
+ *
+ * @hide
+ */
+public final class MultiVibratorInfo extends VibratorInfo {
+ private static final String TAG = "MultiVibratorInfo";
+
+ // Epsilon used for float comparison applied in calculations for the merged info.
+ private static final float EPSILON = 1e-5f;
+
+ public MultiVibratorInfo(int id, VibratorInfo[] vibrators) {
+ this(id, vibrators, frequencyProfileIntersection(vibrators));
+ }
+
+ private MultiVibratorInfo(
+ int id, VibratorInfo[] vibrators, VibratorInfo.FrequencyProfile mergedProfile) {
+ super(id,
+ capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
+ 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),
+ mergedProfile);
+ }
+
+ private static int capabilitiesIntersection(VibratorInfo[] infos,
+ boolean frequencyProfileIsEmpty) {
+ int intersection = ~0;
+ for (VibratorInfo info : infos) {
+ intersection &= info.getCapabilities();
+ }
+ if (frequencyProfileIsEmpty) {
+ // Revoke frequency control if the merged frequency profile ended up empty.
+ intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
+ }
+ 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) != Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
+ // One vibrator doesn't support this effect, so the intersection doesn't.
+ continue effectIdLoop;
+ }
+ }
+
+ intersection.put(effectId, true);
+ }
+
+ return intersection;
+ }
+
+ @NonNull
+ private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
+ VibratorInfo[] infos) {
+ SparseIntArray intersection = new SparseIntArray();
+ SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
+
+ primitiveIdLoop:
+ for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
+ int primitiveId = firstVibratorPrimitives.keyAt(i);
+ int primitiveDuration = firstVibratorPrimitives.valueAt(i);
+ if (primitiveDuration == 0) {
+ // The first vibrator already doesn't support this primitive, so skip it.
+ continue primitiveIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
+ if (vibratorPrimitiveDuration == 0) {
+ // One vibrator doesn't support this primitive, so the intersection doesn't.
+ continue primitiveIdLoop;
+ } else {
+ // The primitive vibration duration is the maximum among all vibrators.
+ primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
+ }
+ }
+
+ intersection.put(primitiveId, primitiveDuration);
+ }
+ return intersection;
+ }
+
+ private static int integerLimitIntersection(VibratorInfo[] infos,
+ Function<VibratorInfo, Integer> propertyGetter) {
+ int limit = 0; // Limit 0 means unlimited
+ for (VibratorInfo info : infos) {
+ int vibratorLimit = propertyGetter.apply(info);
+ if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
+ // This vibrator is limited and intersection is unlimited or has a larger limit:
+ // use smaller limit here for the intersection.
+ limit = vibratorLimit;
+ }
+ }
+ return limit;
+ }
+
+ private static float floatPropertyIntersection(VibratorInfo[] infos,
+ Function<VibratorInfo, Float> propertyGetter) {
+ float property = propertyGetter.apply(infos[0]);
+ if (Float.isNaN(property)) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return Float.NaN;
+ }
+ for (int i = 1; i < infos.length; i++) {
+ if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
+ // If one vibrator has a different value then the intersection is undefined.
+ return Float.NaN;
+ }
+ }
+ return property;
+ }
+
+ @NonNull
+ private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
+ float freqResolution = floatPropertyIntersection(infos,
+ info -> info.getFrequencyProfile().getFrequencyResolutionHz());
+ float resonantFreq = floatPropertyIntersection(infos,
+ VibratorInfo::getResonantFrequencyHz);
+ Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
+
+ if ((freqRange == null) || Float.isNaN(freqResolution)) {
+ return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
+ }
+
+ int amplitudeCount =
+ Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
+ float[] maxAmplitudes = new float[amplitudeCount];
+
+ // Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
+ // will fail if the loop below is broken and do not replace filled values with actual
+ // vibrator measurements.
+ Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
+
+ for (VibratorInfo info : infos) {
+ Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
+ float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
+ int vibratorStartIdx = Math.round(
+ (freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
+ int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
+
+ if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
+ Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
+ + " profiles: attempted to fetch from vibrator "
+ + info.getId() + " max amplitude with bad index " + vibratorStartIdx);
+ return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
+ }
+
+ for (int i = 0; i < maxAmplitudes.length; i++) {
+ maxAmplitudes[i] = Math.min(maxAmplitudes[i],
+ vibratorMaxAmplitudes[vibratorStartIdx + i]);
+ }
+ }
+
+ return new FrequencyProfile(resonantFreq, freqRange.getLower(),
+ freqResolution, maxAmplitudes);
+ }
+
+ @Nullable
+ private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
+ float frequencyResolution) {
+ Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
+ if (firstRange == null) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return null;
+ }
+ float intersectionLower = firstRange.getLower();
+ float intersectionUpper = firstRange.getUpper();
+
+ // Generate the intersection of all vibrator supported ranges, making sure that both
+ // min supported frequencies are aligned w.r.t. the frequency resolution.
+
+ for (int i = 1; i < infos.length; i++) {
+ Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
+ if (vibratorRange == null) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return null;
+ }
+
+ if ((vibratorRange.getLower() >= intersectionUpper)
+ || (vibratorRange.getUpper() <= intersectionLower)) {
+ // If the range and intersection are disjoint then the intersection is undefined
+ return null;
+ }
+
+ float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
+ if ((frequencyDelta % frequencyResolution) > EPSILON) {
+ // If the intersection is not aligned with one vibrator then it's undefined
+ return null;
+ }
+
+ intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
+ intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
+ }
+
+ if ((intersectionUpper - intersectionLower) < frequencyResolution) {
+ // If the intersection is empty then it's undefined.
+ return null;
+ }
+
+ return Range.create(intersectionLower, intersectionUpper);
+ }
+}
diff --git a/core/java/android/os/vibrator/VibratorInfoFactory.java b/core/java/android/os/vibrator/VibratorInfoFactory.java
new file mode 100644
index 0000000..d10d7ec
--- /dev/null
+++ b/core/java/android/os/vibrator/VibratorInfoFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 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.NonNull;
+import android.os.VibratorInfo;
+
+/**
+ * Factory for creating {@link VibratorInfo}s.
+ *
+ * @hide
+ */
+public final class VibratorInfoFactory {
+ /**
+ * Creates a single {@link VibratorInfo} that is an intersection of a given collection of
+ * {@link VibratorInfo}s. That is, the capabilities of the returned info will be an
+ * intersection of that of the provided infos.
+ *
+ * @param id the ID for the new {@link VibratorInfo}.
+ * @param vibratorInfos the {@link VibratorInfo}s from which to create a single
+ * {@link VibratorInfo}.
+ * @return a {@link VibratorInfo} that represents the intersection of {@code vibratorInfos}.
+ */
+ @NonNull
+ public static VibratorInfo create(int id, @NonNull VibratorInfo[] vibratorInfos) {
+ if (vibratorInfos.length == 0) {
+ return new VibratorInfo.Builder(id).build();
+ }
+ if (vibratorInfos.length == 1) {
+ // Create an equivalent info with the requested ID.
+ return new VibratorInfo(id, vibratorInfos[0]);
+ }
+ // Create a MultiVibratorInfo that intersects all the given infos and has the requested ID.
+ return new MultiVibratorInfo(id, vibratorInfos);
+ }
+
+ private VibratorInfoFactory() {}
+}
diff --git a/core/tests/vibrator/src/android/os/VibratorInfoTest.java b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
index 808c4ec..73cd464 100644
--- a/core/tests/vibrator/src/android/os/VibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
@@ -257,8 +257,13 @@
@Test
public void testEquals() {
- VibratorInfo.Builder completeBuilder = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ VibratorInfo.Builder completeBuilder = new VibratorInfo.Builder(TEST_VIBRATOR_ID);
+ // Create a builder with a different ID, but same properties the same as the first one.
+ VibratorInfo.Builder completeBuilder2 = new VibratorInfo.Builder(TEST_VIBRATOR_ID + 2);
+
+ for (VibratorInfo.Builder builder :
+ new VibratorInfo.Builder[] {completeBuilder, completeBuilder2}) {
+ builder.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setPrimitiveDelayMax(100)
@@ -268,31 +273,43 @@
.setPwleSizeMax(20)
.setQFactor(2f)
.setFrequencyProfile(TEST_FREQUENCY_PROFILE);
+ }
VibratorInfo complete = completeBuilder.build();
assertEquals(complete, complete);
+ assertTrue(complete.equalContent(complete));
assertEquals(complete, completeBuilder.build());
+ assertTrue(complete.equalContent(completeBuilder.build()));
assertEquals(complete.hashCode(), completeBuilder.build().hashCode());
+ // The infos from the two builders should have equal content, but should not be equal due to
+ // their different IDs.
+ assertNotEquals(complete, completeBuilder2.build());
+ assertTrue(complete.equalContent(completeBuilder2.build()));
+
VibratorInfo completeWithComposeControl = completeBuilder
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.build();
assertNotEquals(complete, completeWithComposeControl);
+ assertFalse(complete.equalContent(completeWithComposeControl));
VibratorInfo completeWithNoEffects = completeBuilder
.setSupportedEffects(new int[0])
.build();
assertNotEquals(complete, completeWithNoEffects);
+ assertFalse(complete.equalContent(completeWithNoEffects));
VibratorInfo completeWithUnknownEffects = completeBuilder
.setSupportedEffects(null)
.build();
assertNotEquals(complete, completeWithUnknownEffects);
+ assertFalse(complete.equalContent(completeWithUnknownEffects));
VibratorInfo completeWithDifferentPrimitiveDuration = completeBuilder
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build();
assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
+ assertFalse(complete.equalContent(completeWithDifferentPrimitiveDuration));
VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
@@ -302,31 +319,37 @@
TEST_AMPLITUDE_MAP))
.build();
assertNotEquals(complete, completeWithDifferentFrequencyProfile);
+ assertFalse(complete.equalContent(completeWithDifferentFrequencyProfile));
VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder
.setFrequencyProfile(EMPTY_FREQUENCY_PROFILE)
.build();
assertNotEquals(complete, completeWithEmptyFrequencyProfile);
+ assertFalse(complete.equalContent(completeWithEmptyFrequencyProfile));
VibratorInfo completeWithUnknownQFactor = completeBuilder.setQFactor(Float.NaN).build();
assertNotEquals(complete, completeWithUnknownQFactor);
+ assertFalse(complete.equalContent(completeWithUnknownQFactor));
VibratorInfo completeWithDifferentQFactor = completeBuilder
.setQFactor(complete.getQFactor() + 3f)
.build();
assertNotEquals(complete, completeWithDifferentQFactor);
+ assertFalse(complete.equalContent(completeWithDifferentQFactor));
VibratorInfo unknownEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
VibratorInfo knownEmptyEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedEffects(new int[0])
.build();
assertNotEquals(unknownEffectSupport, knownEmptyEffectSupport);
+ assertFalse(unknownEffectSupport.equalContent(knownEmptyEffectSupport));
VibratorInfo unknownBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
VibratorInfo knownEmptyBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedBraking(new int[0])
.build();
assertNotEquals(unknownBrakingSupport, knownEmptyBrakingSupport);
+ assertFalse(unknownBrakingSupport.equalContent(knownEmptyBrakingSupport));
}
@Test
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index 8141ca4..cfa12bb 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -37,7 +37,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
-import android.hardware.vibrator.IVibrator;
import android.media.AudioAttributes;
import android.os.test.TestLooper;
@@ -60,8 +59,6 @@
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
- private static final float TEST_TOLERANCE = 1e-5f;
-
private Context mContextSpy;
private Vibrator mVibratorSpy;
private TestLooper mTestLooper;
@@ -79,9 +76,6 @@
@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
@@ -95,53 +89,6 @@
}
@Test
- public void areEffectsSupported_noVibrator_returnsAlwaysNo() {
- VibratorInfo info = new SystemVibrator.NoVibratorInfo();
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- }
-
- @Test
- public void areEffectsSupported_unsupportedInOneVibrator_returnsNo() {
- VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
- .build();
- VibratorInfo unsupportedVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setSupportedEffects(new int[0])
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- }
-
- @Test
- public void areEffectsSupported_unknownInOneVibrator_returnsUnknown() {
- VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
- .build();
- VibratorInfo unknownSupportVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{supportedVibrator, unknownSupportVibrator});
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- }
-
- @Test
- public void arePrimitivesSupported_supportedInAllVibrators_returnsYes() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, secondVibrator});
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- }
-
- @Test
public void arePrimitivesSupported_returnsArrayOfSameSize() {
assertEquals(0, mVibratorSpy.arePrimitivesSupported(new int[0]).length);
assertEquals(1, mVibratorSpy.arePrimitivesSupported(
@@ -152,39 +99,6 @@
}
@Test
- public void arePrimitivesSupported_noVibrator_returnsAlwaysFalse() {
- VibratorInfo info = new SystemVibrator.NoVibratorInfo();
- assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
- public void arePrimitivesSupported_unsupportedInOneVibrator_returnsFalse() {
- VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build();
- VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
- assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
- public void arePrimitivesSupported_supportedInAllVibrators_returnsTrue() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 5)
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 15)
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, secondVibrator});
- assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
public void getPrimitivesDurations_returnsArrayOfSameSize() {
assertEquals(0, mVibratorSpy.getPrimitiveDurations(new int[0]).length);
assertEquals(1, mVibratorSpy.getPrimitiveDurations(
@@ -195,245 +109,6 @@
}
@Test
- public void getPrimitivesDurations_noVibrator_returnsAlwaysZero() {
- VibratorInfo info = new SystemVibrator.NoVibratorInfo();
- assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
- public void getPrimitivesDurations_unsupportedInOneVibrator_returnsZero() {
- VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build();
- VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
- assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
- public void getPrimitivesDurations_supportedInAllVibrators_returnsMaxDuration() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
- .build();
- 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)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setQFactor(1f)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .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()));
- assertEmptyFrequencyProfileAndControl(info);
-
- // 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()));
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setQFactor(10f)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
- /* resonantFrequencyHz= */ 11, 10, 0.5f, null))
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .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);
-
- // No frequency range defined.
- assertTrue(info.getFrequencyProfile().isEmpty());
- assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
- }
-
- @Test
- public void getFrequencyProfile_noVibrator_returnsEmpty() {
- VibratorInfo info = new SystemVibrator.NoVibratorInfo();
-
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
- new float[] { 0, 1 }))
- .build();
- VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
- new float[] { 0, 1 }))
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, differentResonantFrequency});
-
- assertEmptyFrequencyProfileAndControl(info);
-
- VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
- new float[] { 0, 1 }))
- .build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
-
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getFrequencyProfile_missingValues_returnsEmpty() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
- new float[] { 0, 1 }))
- .build();
- VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
- new float[] { 0, 1 }))
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, missingResonantFrequency});
-
- assertEmptyFrequencyProfileAndControl(info);
-
- VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
- new float[] { 0, 1 }))
- .build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, missingMinFrequency});
-
- assertEmptyFrequencyProfileAndControl(info);
-
- VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
- new float[] { 0, 1 }))
- .build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
-
- assertEmptyFrequencyProfileAndControl(info);
-
- VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
- .build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
-
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
- new float[] { 0, 1, 1, 0 }))
- .build();
- VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
- new float[] { 0, 1, 1, 0 }))
- .build();
- VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .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});
-
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
- new float[] { 0.5f, 1, 1, 0.5f }))
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
- new float[] { 1, 1, 1 }))
- .build();
- VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .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());
- assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
-
- // Third vibrator without frequency control capability.
- 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();
- 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());
- assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
- }
-
- @Test
public void onVibratorStateChanged_noVibrator_registersNoListenerToVibratorManager() {
VibratorManager mockVibratorManager = mock(VibratorManager.class);
when(mockVibratorManager.getVibratorIds()).thenReturn(new int[0]);
@@ -577,12 +252,4 @@
VibrationAttributes vibrationAttributes = captor.getValue();
assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
}
-
- /**
- * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
- */
- void assertEmptyFrequencyProfileAndControl(VibratorInfo info) {
- assertTrue(info.getFrequencyProfile().isEmpty());
- assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
- }
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java b/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
new file mode 100644
index 0000000..fc31ac4
--- /dev/null
+++ b/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2023 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 static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MultiVibratorInfoTest {
+ private static final float TEST_TOLERANCE = 1e-5f;
+
+ @Test
+ public void testGetId() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setSupportedEffects(new int[0])
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 3,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertEquals(3, info.getId());
+ }
+
+ @Test
+ public void testIsEffectSupported_supportedInAllVibrators_returnsYes() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK)
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
+ info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
+ }
+
+ @Test
+ public void testIsEffectSupported_unsupportedInOneVibrator_returnsNo() {
+ VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo unsupportedVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setSupportedEffects(new int[0])
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
+
+ assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
+ info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
+ }
+
+ @Test
+ public void testIsEffectSupported_unknownInOneVibrator_returnsUnknown() {
+ VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo unknownSupportVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{supportedVibrator, unknownSupportVibrator});
+ assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
+ info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
+ }
+
+ @Test
+ public void testIsPrimitiveSupported_unsupportedInOneVibrator_returnsFalse() {
+ VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+ VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
+
+ assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
+ }
+
+ @Test
+ public void testIsPrimitiveSupported_supportedInAllVibrators_returnsTrue() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 5)
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 15)
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
+ }
+
+ @Test
+ public void testGetPrimitiveDuration_unsupportedInOneVibrator_returnsZero() {
+ VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+ VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
+
+ assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
+ }
+
+ @Test
+ public void testGetPrimitiveDuration_supportedInAllVibrators_returnsMaxDuration() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
+ }
+
+ @Test
+ public void testGetQFactorAndResonantFrequency_differentValues_returnsNaN() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setQFactor(1f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setQFactor(2f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
+
+ // One vibrator with values undefined.
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, thirdVibrator});
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
+ }
+
+ @Test
+ public void testGetQFactorAndResonantFrequency_sameValues_returnsValue() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setQFactor(10f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 11, 10, 0.5f, null))
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setQFactor(10f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 11, 5, 1, null))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
+ assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+ // No frequency range defined.
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+ }
+
+ @Test
+ public void testGetFrequencyProfile_differentResonantFrequencyOrResolutions_returnsEmpty() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, differentResonantFrequency});
+
+ assertEmptyFrequencyProfileAndControl(info);
+
+ VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
+ new float[] { 0, 1 }))
+ .build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, differentFrequencyResolution});
+
+ assertEmptyFrequencyProfileAndControl(info);
+ }
+
+ @Test
+ public void testGetFrequencyProfile_missingValues_returnsEmpty() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, missingResonantFrequency});
+
+ assertEmptyFrequencyProfileAndControl(info);
+
+ VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
+ new float[] { 0, 1 }))
+ .build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, missingMinFrequency});
+
+ assertEmptyFrequencyProfileAndControl(info);
+
+ VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
+ new float[] { 0, 1 }))
+ .build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, missingFrequencyResolution});
+
+ assertEmptyFrequencyProfileAndControl(info);
+
+ VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+ .build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, missingMaxAmplitudes});
+
+ assertEmptyFrequencyProfileAndControl(info);
+ }
+
+ @Test
+ public void testGetFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, unalignedMinFrequency, thirdVibrator});
+
+ assertEmptyFrequencyProfileAndControl(info);
+ }
+
+ @Test
+ public void testGetFrequencyProfile_alignedProfiles_returnsIntersection() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+ new float[] { 0.5f, 1, 1, 0.5f }))
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 1, 1, 1 }))
+ .build();
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0.8f, 1, 0.8f, 0.5f }))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo, thirdVibrator});
+
+ assertEquals(
+ new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+ info.getFrequencyProfile());
+ assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+
+ // Third vibrator without frequency control capability.
+ 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();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo, thirdVibrator});
+
+ assertEquals(
+ new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+ info.getFrequencyProfile());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+ }
+
+ /**
+ * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
+ */
+ private void assertEmptyFrequencyProfileAndControl(VibratorInfo info) {
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+ }
+}
diff --git a/core/tests/vibrator/src/android/os/vibrator/VibratorInfoFactoryTest.java b/core/tests/vibrator/src/android/os/vibrator/VibratorInfoFactoryTest.java
new file mode 100644
index 0000000..df4822f
--- /dev/null
+++ b/core/tests/vibrator/src/android/os/vibrator/VibratorInfoFactoryTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2023 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 static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class VibratorInfoFactoryTest {
+
+ @Test
+ public void testCreatedInfo_hasTheRequestedId() {
+ // Empty info list.
+ VibratorInfo infoFromEmptyInfos =
+ VibratorInfoFactory.create(/* id= */ 3, new VibratorInfo[] {});
+ VibratorInfo info1 = new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo info2 = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+ VibratorInfo infoFromOneInfo =
+ VibratorInfoFactory.create(/* id= */ -1, new VibratorInfo[] {info1});
+ VibratorInfo infoFromTwoInfos =
+ VibratorInfoFactory.create(/* id= */ -3, new VibratorInfo[] {info1, info2});
+
+ assertEquals(3, infoFromEmptyInfos.getId());
+ assertEquals(-1, infoFromOneInfo.getId());
+ assertEquals(-3, infoFromTwoInfos.getId());
+ }
+
+ @Test
+ public void testCreatedInfo_fromEmptyVibratorInfos_returnsEmptyVibratorInfo() {
+ VibratorInfo info = VibratorInfoFactory.create(/* id= */ 2, new VibratorInfo[] {});
+
+ assertEqualContent(VibratorInfo.EMPTY_VIBRATOR_INFO, info);
+ }
+
+ @Test
+ public void testCreatedInfo_fromSingleVibratorInfo_hasEqualContent() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_FREQUENCY_CONTROL)
+ .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_THUD)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 30)
+ .build();
+
+ VibratorInfo createdInfo =
+ VibratorInfoFactory.create(/* id= */ -1, new VibratorInfo[] {info});
+
+ assertEqualContent(info, createdInfo);
+ }
+
+ @Test
+ public void testCreatedInfo_hasEqualContentRegardlessOfSourceInfoOrder() {
+ VibratorInfo info1 = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo info2 = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+
+ assertEqualContent(
+ VibratorInfoFactory.create(/* id= */ -1, new VibratorInfo[] {info1, info2}),
+ VibratorInfoFactory.create(/* id= */ -1, new VibratorInfo[] {info2, info1}));
+ }
+
+ @Test
+ public void testCreatedInfoContents() {
+ VibratorInfo info1 = new VibratorInfo.Builder(/* id= */ -1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_FREQUENCY_CONTROL)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_POP)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 5)
+ .build();
+ VibratorInfo info2 = new VibratorInfo.Builder(/* id= */ -2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
+ .setSupportedEffects(VibrationEffect.EFFECT_POP, VibrationEffect.EFFECT_THUD)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 20)
+ .build();
+ VibratorInfo info3 = new VibratorInfo.Builder(/* id= */ -3)
+ .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ .build();
+
+ assertEquals(
+ new VibratorInfo.Builder(/* id= */ 3)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedEffects(VibrationEffect.EFFECT_POP)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 20)
+ .build(),
+ VibratorInfoFactory.create(/* id= */ 3, new VibratorInfo[] {info1, info2}));
+ assertEquals(
+ new VibratorInfo.Builder(/* id= */ 3)
+ .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ .build(),
+ VibratorInfoFactory.create(/* id= */ 3, new VibratorInfo[] {info2, info3}));
+ assertEquals(
+ new VibratorInfo.Builder(/* id= */ 3).build(),
+ VibratorInfoFactory.create(/* id= */ 3, new VibratorInfo[] {info1, info3}));
+ }
+
+ private static void assertEqualContent(VibratorInfo info1, VibratorInfo info2) {
+ assertTrue(info1.equalContent(info2));
+ }
+}