diff options
author | 2023-08-18 18:45:38 +0000 | |
---|---|---|
committer | 2023-08-18 18:45:38 +0000 | |
commit | ded17f5913adeb48a7f367dba74eb33ad1cb56bf (patch) | |
tree | 1ef2bfd8ccbff7f51536b0640660ad1e1d8887aa | |
parent | deb82d45d2742e0a69f3f59760caca3c5e4d481e (diff) | |
parent | 915513c99a2c1f662c04eeefd5d2dea266b1943f (diff) |
Merge "Extract VibratorInfo aggregation logic to a factory" into main
8 files changed, 878 insertions, 642 deletions
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index bf72b1d7a035..2cda787082c7 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -18,26 +18,19 @@ 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.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 @@ public class SystemVibrator extends Vibrator { 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 @@ public class SystemVibrator extends Vibrator { } 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 @@ public class SystemVibrator extends Vibrator { } /** - * 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 0b7d7c3cb877..4f8c24d1f905 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -156,6 +156,16 @@ public class VibratorInfo implements Parcelable { 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 @@ public class VibratorInfo implements Parcelable { 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 @@ public class VibratorInfo implements Parcelable { 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 000000000000..5f3273129213 --- /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 000000000000..d10d7ec222c3 --- /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 808c4ece9435..73cd4647415d 100644 --- a/core/tests/vibrator/src/android/os/VibratorInfoTest.java +++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java @@ -257,8 +257,13 @@ public class VibratorInfoTest { @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 @@ public class VibratorInfoTest { .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 @@ public class VibratorInfoTest { 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 8141ca4b22a8..cfa12bb5b504 100644 --- a/core/tests/vibrator/src/android/os/VibratorTest.java +++ b/core/tests/vibrator/src/android/os/VibratorTest.java @@ -37,7 +37,6 @@ import static org.mockito.Mockito.when; 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 @@ public class VibratorTest { @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 @@ 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 @@ -95,53 +89,6 @@ public class VibratorTest { } @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 @@ public class VibratorTest { } @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 @@ public class VibratorTest { } @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 @@ public class VibratorTest { 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 000000000000..fc31ac44b362 --- /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 000000000000..df4822fc8b04 --- /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)); + } +} |