Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ded17f59 authored by Yeabkal Wubshit's avatar Yeabkal Wubshit Committed by Android (Google) Code Review
Browse files

Merge "Extract VibratorInfo aggregation logic to a factory" into main

parents deb82d45 915513c9
Loading
Loading
Loading
Loading
+3 −305
Original line number Original line Diff line number Diff line
@@ -18,26 +18,19 @@ package android.os;


import android.annotation.CallbackExecutor;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Context;
import android.hardware.vibrator.IVibrator;
import android.os.vibrator.VibratorInfoFactory;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Log;
import android.util.Range;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;


import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;


import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
import java.util.function.Function;


/**
/**
 * Vibrator implementation that controls the main system vibrator.
 * Vibrator implementation that controls the main system vibrator.
@@ -82,7 +75,7 @@ public class SystemVibrator extends Vibrator {
            if (vibratorIds.length == 0) {
            if (vibratorIds.length == 0) {
                // It is known that the device has no vibrator, so cache and return info that
                // It is known that the device has no vibrator, so cache and return info that
                // reflects the lack of support for effects/primitives.
                // reflects the lack of support for effects/primitives.
                return mVibratorInfo = new NoVibratorInfo();
                return mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
            }
            }
            VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
            VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
            for (int i = 0; i < vibratorIds.length; i++) {
            for (int i = 0; i < vibratorIds.length; i++) {
@@ -96,12 +89,7 @@ public class SystemVibrator extends Vibrator {
                }
                }
                vibratorInfos[i] = vibrator.getInfo();
                vibratorInfos[i] = vibrator.getInfo();
            }
            }
            if (vibratorInfos.length == 1) {
            return mVibratorInfo = VibratorInfoFactory.create(/* id= */ -1, vibratorInfos);
                // 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);
        }
        }
    }
    }


@@ -274,296 +262,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.
     * Listener for all vibrators state change.
     *
     *
+13 −2
Original line number Original line Diff line number Diff line
@@ -156,6 +156,16 @@ public class VibratorInfo implements Parcelable {
            return false;
            return false;
        }
        }
        VibratorInfo that = (VibratorInfo) o;
        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();
        int supportedPrimitivesCount = mSupportedPrimitives.size();
        if (supportedPrimitivesCount != that.mSupportedPrimitives.size()) {
        if (supportedPrimitivesCount != that.mSupportedPrimitives.size()) {
            return false;
            return false;
@@ -168,7 +178,7 @@ public class VibratorInfo implements Parcelable {
                return false;
                return false;
            }
            }
        }
        }
        return mId == that.mId && mCapabilities == that.mCapabilities
        return mCapabilities == that.mCapabilities
                && mPrimitiveDelayMax == that.mPrimitiveDelayMax
                && mPrimitiveDelayMax == that.mPrimitiveDelayMax
                && mCompositionSizeMax == that.mCompositionSizeMax
                && mCompositionSizeMax == that.mCompositionSizeMax
                && mPwlePrimitiveDurationMax == that.mPwlePrimitiveDurationMax
                && mPwlePrimitiveDurationMax == that.mPwlePrimitiveDurationMax
@@ -445,7 +455,8 @@ public class VibratorInfo implements Parcelable {
        return mFrequencyProfile;
        return mFrequencyProfile;
    }
    }


    protected long getCapabilities() {
    /** Returns a single int representing all the capabilities of the vibrator. */
    public long getCapabilities() {
        return mCapabilities;
        return mCapabilities;
    }
    }


+294 −0

File added.

Preview size limit exceeded, changes collapsed.

+52 −0
Original line number Original line Diff line number Diff line
/*
 * 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() {}
}
+25 −2
Original line number Original line Diff line number Diff line
@@ -257,8 +257,13 @@ public class VibratorInfoTest {


    @Test
    @Test
    public void testEquals() {
    public void testEquals() {
        VibratorInfo.Builder completeBuilder = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
        VibratorInfo.Builder completeBuilder = new VibratorInfo.Builder(TEST_VIBRATOR_ID);
                .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
        // 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)
                .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
                .setPrimitiveDelayMax(100)
                .setPrimitiveDelayMax(100)
@@ -268,31 +273,43 @@ public class VibratorInfoTest {
                .setPwleSizeMax(20)
                .setPwleSizeMax(20)
                .setQFactor(2f)
                .setQFactor(2f)
                .setFrequencyProfile(TEST_FREQUENCY_PROFILE);
                .setFrequencyProfile(TEST_FREQUENCY_PROFILE);
        }
        VibratorInfo complete = completeBuilder.build();
        VibratorInfo complete = completeBuilder.build();


        assertEquals(complete, complete);
        assertEquals(complete, complete);
        assertTrue(complete.equalContent(complete));
        assertEquals(complete, completeBuilder.build());
        assertEquals(complete, completeBuilder.build());
        assertTrue(complete.equalContent(completeBuilder.build()));
        assertEquals(complete.hashCode(), completeBuilder.build().hashCode());
        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
        VibratorInfo completeWithComposeControl = completeBuilder
                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
                .build();
                .build();
        assertNotEquals(complete, completeWithComposeControl);
        assertNotEquals(complete, completeWithComposeControl);
        assertFalse(complete.equalContent(completeWithComposeControl));


        VibratorInfo completeWithNoEffects = completeBuilder
        VibratorInfo completeWithNoEffects = completeBuilder
                .setSupportedEffects(new int[0])
                .setSupportedEffects(new int[0])
                .build();
                .build();
        assertNotEquals(complete, completeWithNoEffects);
        assertNotEquals(complete, completeWithNoEffects);
        assertFalse(complete.equalContent(completeWithNoEffects));


        VibratorInfo completeWithUnknownEffects = completeBuilder
        VibratorInfo completeWithUnknownEffects = completeBuilder
                .setSupportedEffects(null)
                .setSupportedEffects(null)
                .build();
                .build();
        assertNotEquals(complete, completeWithUnknownEffects);
        assertNotEquals(complete, completeWithUnknownEffects);
        assertFalse(complete.equalContent(completeWithUnknownEffects));


        VibratorInfo completeWithDifferentPrimitiveDuration = completeBuilder
        VibratorInfo completeWithDifferentPrimitiveDuration = completeBuilder
                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
                .build();
                .build();
        assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
        assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
        assertFalse(complete.equalContent(completeWithDifferentPrimitiveDuration));


        VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
        VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
@@ -302,31 +319,37 @@ public class VibratorInfoTest {
                        TEST_AMPLITUDE_MAP))
                        TEST_AMPLITUDE_MAP))
                .build();
                .build();
        assertNotEquals(complete, completeWithDifferentFrequencyProfile);
        assertNotEquals(complete, completeWithDifferentFrequencyProfile);
        assertFalse(complete.equalContent(completeWithDifferentFrequencyProfile));


        VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder
        VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder
                .setFrequencyProfile(EMPTY_FREQUENCY_PROFILE)
                .setFrequencyProfile(EMPTY_FREQUENCY_PROFILE)
                .build();
                .build();
        assertNotEquals(complete, completeWithEmptyFrequencyProfile);
        assertNotEquals(complete, completeWithEmptyFrequencyProfile);
        assertFalse(complete.equalContent(completeWithEmptyFrequencyProfile));


        VibratorInfo completeWithUnknownQFactor = completeBuilder.setQFactor(Float.NaN).build();
        VibratorInfo completeWithUnknownQFactor = completeBuilder.setQFactor(Float.NaN).build();
        assertNotEquals(complete, completeWithUnknownQFactor);
        assertNotEquals(complete, completeWithUnknownQFactor);
        assertFalse(complete.equalContent(completeWithUnknownQFactor));


        VibratorInfo completeWithDifferentQFactor = completeBuilder
        VibratorInfo completeWithDifferentQFactor = completeBuilder
                .setQFactor(complete.getQFactor() + 3f)
                .setQFactor(complete.getQFactor() + 3f)
                .build();
                .build();
        assertNotEquals(complete, completeWithDifferentQFactor);
        assertNotEquals(complete, completeWithDifferentQFactor);
        assertFalse(complete.equalContent(completeWithDifferentQFactor));


        VibratorInfo unknownEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
        VibratorInfo unknownEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
        VibratorInfo knownEmptyEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
        VibratorInfo knownEmptyEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
                .setSupportedEffects(new int[0])
                .setSupportedEffects(new int[0])
                .build();
                .build();
        assertNotEquals(unknownEffectSupport, knownEmptyEffectSupport);
        assertNotEquals(unknownEffectSupport, knownEmptyEffectSupport);
        assertFalse(unknownEffectSupport.equalContent(knownEmptyEffectSupport));


        VibratorInfo unknownBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
        VibratorInfo unknownBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
        VibratorInfo knownEmptyBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
        VibratorInfo knownEmptyBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
                .setSupportedBraking(new int[0])
                .setSupportedBraking(new int[0])
                .build();
                .build();
        assertNotEquals(unknownBrakingSupport, knownEmptyBrakingSupport);
        assertNotEquals(unknownBrakingSupport, knownEmptyBrakingSupport);
        assertFalse(unknownBrakingSupport.equalContent(knownEmptyBrakingSupport));
    }
    }


    @Test
    @Test
Loading