/* * Copyright (C) 2020 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; import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.vibrator.Braking; import android.hardware.vibrator.IVibrator; import android.util.IndentingPrintWriter; import android.util.MathUtils; import android.util.Range; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** * A VibratorInfo describes the capabilities of a {@link Vibrator}. * *

This description includes its capabilities, list of supported effects and composition * primitives. * * @hide */ public class VibratorInfo implements Parcelable { private static final String TAG = "VibratorInfo"; /** @hide */ public static final VibratorInfo EMPTY_VIBRATOR_INFO = new VibratorInfo.Builder(-1).build(); private final int mId; private final long mCapabilities; @Nullable private final SparseBooleanArray mSupportedEffects; @Nullable private final SparseBooleanArray mSupportedBraking; private final SparseIntArray mSupportedPrimitives; private final int mPrimitiveDelayMax; private final int mCompositionSizeMax; private final int mPwlePrimitiveDurationMax; private final int mPwleSizeMax; private final float mQFactor; private final FrequencyProfile mFrequencyProfile; VibratorInfo(Parcel in) { mId = in.readInt(); mCapabilities = in.readLong(); mSupportedEffects = in.readSparseBooleanArray(); mSupportedBraking = in.readSparseBooleanArray(); mSupportedPrimitives = in.readSparseIntArray(); mPrimitiveDelayMax = in.readInt(); mCompositionSizeMax = in.readInt(); mPwlePrimitiveDurationMax = in.readInt(); mPwleSizeMax = in.readInt(); mQFactor = in.readFloat(); mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in); } public VibratorInfo(int id, @NonNull VibratorInfo baseVibratorInfo) { this(id, baseVibratorInfo.mCapabilities, baseVibratorInfo.mSupportedEffects, baseVibratorInfo.mSupportedBraking, baseVibratorInfo.mSupportedPrimitives, baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax, baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax, baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfile); } /** * Default constructor. * * @param id The vibrator id. * @param capabilities All capability flags of the vibrator, defined in * IVibrator.CAP_*. * @param supportedEffects All supported predefined effects, enum values from * {@link android.hardware.vibrator.Effect}. * @param supportedBraking All supported braking types, enum values from {@link * Braking}. * @param supportedPrimitives All supported primitive effects, key are enum values from * {@link android.hardware.vibrator.CompositePrimitive} and * values are estimated durations in milliseconds. * @param primitiveDelayMax The maximum delay that can be set to a composition primitive * in milliseconds. * @param compositionSizeMax The maximum number of primitives supported by a composition. * @param pwlePrimitiveDurationMax The maximum duration of a PWLE primitive in milliseconds. * @param pwleSizeMax The maximum number of primitives supported by a PWLE * composition. * @param qFactor The vibrator quality factor. * @param frequencyProfile The description of the vibrator supported frequencies and max * amplitude mappings. * @hide */ public VibratorInfo(int id, long capabilities, @Nullable SparseBooleanArray supportedEffects, @Nullable SparseBooleanArray supportedBraking, @NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax, int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax, float qFactor, @NonNull FrequencyProfile frequencyProfile) { Preconditions.checkNotNull(supportedPrimitives); Preconditions.checkNotNull(frequencyProfile); mId = id; mCapabilities = capabilities; mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone(); mSupportedBraking = supportedBraking == null ? null : supportedBraking.clone(); mSupportedPrimitives = supportedPrimitives.clone(); mPrimitiveDelayMax = primitiveDelayMax; mCompositionSizeMax = compositionSizeMax; mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax; mPwleSizeMax = pwleSizeMax; mQFactor = qFactor; mFrequencyProfile = frequencyProfile; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mId); dest.writeLong(mCapabilities); dest.writeSparseBooleanArray(mSupportedEffects); dest.writeSparseBooleanArray(mSupportedBraking); dest.writeSparseIntArray(mSupportedPrimitives); dest.writeInt(mPrimitiveDelayMax); dest.writeInt(mCompositionSizeMax); dest.writeInt(mPwlePrimitiveDurationMax); dest.writeInt(mPwleSizeMax); dest.writeFloat(mQFactor); mFrequencyProfile.writeToParcel(dest, flags); } @Override public int describeContents() { return 0; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof VibratorInfo)) { 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; } for (int i = 0; i < supportedPrimitivesCount; i++) { if (mSupportedPrimitives.keyAt(i) != that.mSupportedPrimitives.keyAt(i)) { return false; } if (mSupportedPrimitives.valueAt(i) != that.mSupportedPrimitives.valueAt(i)) { return false; } } return mCapabilities == that.mCapabilities && mPrimitiveDelayMax == that.mPrimitiveDelayMax && mCompositionSizeMax == that.mCompositionSizeMax && mPwlePrimitiveDurationMax == that.mPwlePrimitiveDurationMax && mPwleSizeMax == that.mPwleSizeMax && Objects.equals(mSupportedEffects, that.mSupportedEffects) && Objects.equals(mSupportedBraking, that.mSupportedBraking) && Objects.equals(mQFactor, that.mQFactor) && Objects.equals(mFrequencyProfile, that.mFrequencyProfile); } @Override public int hashCode() { int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking, mQFactor, mFrequencyProfile); for (int i = 0; i < mSupportedPrimitives.size(); i++) { hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i); hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i); } return hashCode; } @Override public String toString() { return "VibratorInfo{" + "mId=" + mId + ", mCapabilities=" + Arrays.toString(getCapabilitiesNames()) + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities) + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames()) + ", mSupportedBraking=" + Arrays.toString(getSupportedBrakingNames()) + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames()) + ", mPrimitiveDelayMax=" + mPrimitiveDelayMax + ", mCompositionSizeMax=" + mCompositionSizeMax + ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax + ", mPwleSizeMax=" + mPwleSizeMax + ", mQFactor=" + mQFactor + ", mFrequencyProfile=" + mFrequencyProfile + '}'; } /** @hide */ public void dump(IndentingPrintWriter pw) { pw.println("VibratorInfo:"); pw.increaseIndent(); pw.println("id = " + mId); pw.println("capabilities = " + Arrays.toString(getCapabilitiesNames())); pw.println("capabilitiesFlags = " + Long.toBinaryString(mCapabilities)); pw.println("supportedEffects = " + Arrays.toString(getSupportedEffectsNames())); pw.println("supportedPrimitives = " + Arrays.toString(getSupportedPrimitivesNames())); pw.println("supportedBraking = " + Arrays.toString(getSupportedBrakingNames())); pw.println("primitiveDelayMax = " + mPrimitiveDelayMax); pw.println("compositionSizeMax = " + mCompositionSizeMax); pw.println("pwlePrimitiveDurationMax = " + mPwlePrimitiveDurationMax); pw.println("pwleSizeMax = " + mPwleSizeMax); pw.println("q-factor = " + mQFactor); pw.println("frequencyProfile = " + mFrequencyProfile); pw.decreaseIndent(); } /** Return the id of this vibrator. */ public int getId() { return mId; } /** * Check whether the vibrator has amplitude control. * * @return True if the hardware can control the amplitude of the vibrations, otherwise false. */ public boolean hasAmplitudeControl() { return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL); } /** * Check whether the vibrator has frequency control. * * @return True if the hardware can control the frequency of the vibrations, otherwise false. */ public boolean hasFrequencyControl() { // We currently can only control frequency of the vibration using the compose PWLE method. return hasCapability( IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS); } /** * Returns a default value to be applied to composed PWLE effects for braking. * * @return a supported braking value, one of android.hardware.vibrator.Braking.* * @hide */ public int getDefaultBraking() { if (mSupportedBraking != null) { int size = mSupportedBraking.size(); for (int i = 0; i < size; i++) { if (mSupportedBraking.keyAt(i) != Braking.NONE) { return mSupportedBraking.keyAt(i); } } } return Braking.NONE; } /** @hide */ @Nullable public SparseBooleanArray getSupportedBraking() { if (mSupportedBraking == null) { return null; } return mSupportedBraking.clone(); } /** @hide */ public boolean isBrakingSupportKnown() { return mSupportedBraking != null; } /** @hide */ public boolean hasBrakingSupport(@Braking int braking) { return (mSupportedBraking != null) && mSupportedBraking.get(braking); } /** @hide */ public boolean isEffectSupportKnown() { return mSupportedEffects != null; } /** * Query whether the vibrator supports the given effect. * * @param effectId Which effects to query for. * @return {@link Vibrator#VIBRATION_EFFECT_SUPPORT_YES} if the effect is supported, * {@link Vibrator#VIBRATION_EFFECT_SUPPORT_NO} if it isn't supported, or * {@link Vibrator#VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether it's * supported or not. */ @Vibrator.VibrationEffectSupport public int isEffectSupported(@VibrationEffect.EffectType int effectId) { if (mSupportedEffects == null) { return Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN; } return mSupportedEffects.get(effectId) ? Vibrator.VIBRATION_EFFECT_SUPPORT_YES : Vibrator.VIBRATION_EFFECT_SUPPORT_NO; } /** @hide */ @Nullable public SparseBooleanArray getSupportedEffects() { if (mSupportedEffects == null) { return null; } return mSupportedEffects.clone(); } /** * Query whether the vibrator supports the given primitive. * * @param primitiveId Which primitives to query for. * @return Whether the primitive is supported. */ public boolean isPrimitiveSupported( @VibrationEffect.Composition.PrimitiveType int primitiveId) { return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && (mSupportedPrimitives.indexOfKey(primitiveId) >= 0); } /** * Query whether or not the vibrator supports all components of a given {@link VibrationEffect} * (i.e. the vibrator can play the given effect as intended). * *

See {@link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more * information on how the vibrator support is determined. * * @param effect the {@link VibrationEffect} to check if it is supported * @return {@code true} if the vibrator can play the given {@code effect} as intended, * {@code false} otherwise. * * @hide */ public boolean areVibrationFeaturesSupported(@NonNull VibrationEffect effect) { return effect.areVibrationFeaturesSupported(this); } /** * Query the estimated duration of given primitive. * * @param primitiveId Which primitives to query for. * @return The duration in milliseconds estimated for the primitive, or zero if primitive not * supported. */ public int getPrimitiveDuration( @VibrationEffect.Composition.PrimitiveType int primitiveId) { return mSupportedPrimitives.get(primitiveId); } /** @hide */ public SparseIntArray getSupportedPrimitives() { return mSupportedPrimitives.clone(); } /** * Query the maximum delay supported for a primitive in a composed effect. * * @return The max delay in milliseconds, or zero if unlimited. */ public int getPrimitiveDelayMax() { return mPrimitiveDelayMax; } /** * Query the maximum number of primitives supported in a composed effect. * * @return The max number of primitives supported, or zero if unlimited. */ public int getCompositionSizeMax() { return mCompositionSizeMax; } /** * Query the maximum duration supported for a primitive in a PWLE composition. * * @return The max duration in milliseconds, or zero if unlimited. */ public int getPwlePrimitiveDurationMax() { return mPwlePrimitiveDurationMax; } /** * Query the maximum number of primitives supported in a PWLE composition. * * @return The max number of primitives supported, or zero if unlimited. */ public int getPwleSizeMax() { return mPwleSizeMax; } /** * Check against this vibrator capabilities. * * @param capability one of IVibrator.CAP_* * @return true if this vibrator has this capability, false otherwise * @hide */ public boolean hasCapability(long capability) { return (mCapabilities & capability) == capability; } /** * Gets the resonant frequency of the vibrator. * * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or * this vibrator is a composite of multiple physical devices. */ public float getResonantFrequencyHz() { return mFrequencyProfile.mResonantFrequencyHz; } /** * Gets the Q factor of the vibrator. * * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or * this vibrator is a composite of multiple physical devices. */ public float getQFactor() { return mQFactor; } /** * Gets the profile of supported frequencies, including the measurements of maximum relative * output acceleration for supported vibration frequencies. * *

If the devices does not have frequency control then the profile should be empty. */ @NonNull public FrequencyProfile getFrequencyProfile() { return mFrequencyProfile; } /** Returns a single int representing all the capabilities of the vibrator. */ public long getCapabilities() { return mCapabilities; } private String[] getCapabilitiesNames() { List names = new ArrayList<>(); if (hasCapability(IVibrator.CAP_ON_CALLBACK)) { names.add("ON_CALLBACK"); } if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) { names.add("PERFORM_CALLBACK"); } if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { names.add("COMPOSE_EFFECTS"); } if (hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) { names.add("COMPOSE_PWLE_EFFECTS"); } if (hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { names.add("ALWAYS_ON_CONTROL"); } if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) { names.add("AMPLITUDE_CONTROL"); } if (hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)) { names.add("FREQUENCY_CONTROL"); } if (hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { names.add("EXTERNAL_CONTROL"); } if (hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) { names.add("EXTERNAL_AMPLITUDE_CONTROL"); } return names.toArray(new String[names.size()]); } private String[] getSupportedEffectsNames() { if (mSupportedEffects == null) { return new String[0]; } String[] names = new String[mSupportedEffects.size()]; for (int i = 0; i < mSupportedEffects.size(); i++) { names[i] = VibrationEffect.effectIdToString(mSupportedEffects.keyAt(i)); } return names; } private String[] getSupportedBrakingNames() { if (mSupportedBraking == null) { return new String[0]; } String[] names = new String[mSupportedBraking.size()]; for (int i = 0; i < mSupportedBraking.size(); i++) { switch (mSupportedBraking.keyAt(i)) { case Braking.NONE: names[i] = "NONE"; break; case Braking.CLAB: names[i] = "CLAB"; break; default: names[i] = Integer.toString(mSupportedBraking.keyAt(i)); } } return names; } private String[] getSupportedPrimitivesNames() { int supportedPrimitivesCount = mSupportedPrimitives.size(); String[] names = new String[supportedPrimitivesCount]; for (int i = 0; i < supportedPrimitivesCount; i++) { names[i] = VibrationEffect.Composition.primitiveToString(mSupportedPrimitives.keyAt(i)) + "(" + mSupportedPrimitives.valueAt(i) + "ms)"; } return names; } /** * Describes the maximum relative output acceleration that can be achieved for each supported * frequency in a specific vibrator. * *

This profile is defined by the following parameters: * *

    *
  1. {@code minFrequencyHz}, {@code resonantFrequencyHz} and {@code frequencyResolutionHz} * provided by the vibrator in hertz. *
  2. {@code maxAmplitudes} a list of values in [0,1] provided by the vibrator, where * {@code maxAmplitudes[i]} represents max supported amplitude at frequency * {@code minFrequencyHz + frequencyResolutionHz * i}. *
  3. {@code maxFrequencyHz = minFrequencyHz * + frequencyResolutionHz * (maxAmplitudes.length-1)} *
* * @hide */ public static final class FrequencyProfile implements Parcelable { @Nullable private final Range mFrequencyRangeHz; private final float mMinFrequencyHz; private final float mResonantFrequencyHz; private final float mFrequencyResolutionHz; private final float[] mMaxAmplitudes; FrequencyProfile(Parcel in) { this(in.readFloat(), in.readFloat(), in.readFloat(), in.createFloatArray()); } /** * Default constructor. * * @param resonantFrequencyHz The vibrator resonant frequency, in hertz. * @param minFrequencyHz Minimum supported frequency, in hertz. * @param frequencyResolutionHz The frequency resolution, in hertz, used by the max * amplitude measurements. * @param maxAmplitudes The max amplitude supported by each supported frequency, * starting at minimum frequency with jumps of frequency * resolution. * @hide */ public FrequencyProfile(float resonantFrequencyHz, float minFrequencyHz, float frequencyResolutionHz, float[] maxAmplitudes) { mMinFrequencyHz = minFrequencyHz; mResonantFrequencyHz = resonantFrequencyHz; mFrequencyResolutionHz = frequencyResolutionHz; mMaxAmplitudes = new float[maxAmplitudes == null ? 0 : maxAmplitudes.length]; if (maxAmplitudes != null) { System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length); } // If any required field is undefined or has a bad value then this profile is invalid. boolean isValid = !Float.isNaN(resonantFrequencyHz) && (resonantFrequencyHz > 0) && !Float.isNaN(minFrequencyHz) && (minFrequencyHz > 0) && !Float.isNaN(frequencyResolutionHz) && (frequencyResolutionHz > 0) && (mMaxAmplitudes.length > 0); // If any max amplitude is outside the allowed range then this profile is invalid. for (int i = 0; i < mMaxAmplitudes.length; i++) { isValid &= (mMaxAmplitudes[i] >= 0) && (mMaxAmplitudes[i] <= 1); } float maxFrequencyHz = isValid ? minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1) : Float.NaN; // If the constraint min < resonant < max is not met then it is invalid. isValid &= !Float.isNaN(maxFrequencyHz) && (resonantFrequencyHz >= minFrequencyHz) && (resonantFrequencyHz <= maxFrequencyHz) && (minFrequencyHz < maxFrequencyHz); mFrequencyRangeHz = isValid ? Range.create(minFrequencyHz, maxFrequencyHz) : null; } /** Returns true if the supported frequency range is empty. */ public boolean isEmpty() { return mFrequencyRangeHz == null; } /** Returns the supported frequency range, in hertz. */ @Nullable public Range getFrequencyRangeHz() { return mFrequencyRangeHz; } /** * Returns the maximum relative amplitude the vibrator can reach while playing at the * given frequency. * * @param frequencyHz frequency, in hertz, for query. * @return A value in [0,1] representing the max relative amplitude supported at the given * frequency. This will return 0 if the frequency is outside the supported range, or if the * supported frequency range is empty. */ public float getMaxAmplitude(float frequencyHz) { if (isEmpty() || Float.isNaN(frequencyHz) || !mFrequencyRangeHz.contains(frequencyHz)) { // Unsupported frequency requested, vibrator cannot play at this frequency. return 0; } // Subtract minFrequencyHz to simplify offset calculations. float mappingFreq = frequencyHz - mMinFrequencyHz; // Find the bucket to interpolate within. // Any calculated index should be safe, except exactly equal to max amplitude can be // one step too high, so constrain it to guarantee safety. int startIdx = MathUtils.constrain( /* amount= */ (int) Math.floor(mappingFreq / mFrequencyResolutionHz), /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1); int nextIdx = MathUtils.constrain( /* amount= */ startIdx + 1, /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1); // Linearly interpolate the amplitudes based on the frequency range of the bucket. return MathUtils.constrainedMap( mMaxAmplitudes[startIdx], mMaxAmplitudes[nextIdx], startIdx * mFrequencyResolutionHz, nextIdx * mFrequencyResolutionHz, mappingFreq); } /** Returns the raw list of maximum relative output accelerations from the vibrator. */ @NonNull public float[] getMaxAmplitudes() { return Arrays.copyOf(mMaxAmplitudes, mMaxAmplitudes.length); } /** Returns the raw frequency resolution used for max amplitude measurements, in hertz. */ public float getFrequencyResolutionHz() { return mFrequencyResolutionHz; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeFloat(mResonantFrequencyHz); dest.writeFloat(mMinFrequencyHz); dest.writeFloat(mFrequencyResolutionHz); dest.writeFloatArray(mMaxAmplitudes); } @Override public int describeContents() { return 0; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof FrequencyProfile)) { return false; } FrequencyProfile that = (FrequencyProfile) o; return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0 && Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0 && Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0 && Arrays.equals(mMaxAmplitudes, that.mMaxAmplitudes); } @Override public int hashCode() { int hashCode = Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz, mFrequencyResolutionHz); hashCode = 31 * hashCode + Arrays.hashCode(mMaxAmplitudes); return hashCode; } @Override public String toString() { return "FrequencyProfile{" + "mFrequencyRange=" + mFrequencyRangeHz + ", mMinFrequency=" + mMinFrequencyHz + ", mResonantFrequency=" + mResonantFrequencyHz + ", mFrequencyResolution=" + mFrequencyResolutionHz + ", mMaxAmplitudes count=" + mMaxAmplitudes.length + '}'; } @NonNull public static final Creator CREATOR = new Creator() { @Override public FrequencyProfile createFromParcel(Parcel in) { return new FrequencyProfile(in); } @Override public FrequencyProfile[] newArray(int size) { return new FrequencyProfile[size]; } }; } /** @hide */ public static final class Builder { private final int mId; private long mCapabilities; private SparseBooleanArray mSupportedEffects; private SparseBooleanArray mSupportedBraking; private SparseIntArray mSupportedPrimitives = new SparseIntArray(); private int mPrimitiveDelayMax; private int mCompositionSizeMax; private int mPwlePrimitiveDurationMax; private int mPwleSizeMax; private float mQFactor = Float.NaN; private FrequencyProfile mFrequencyProfile = new FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null); /** A builder class for a {@link VibratorInfo}. */ public Builder(int id) { mId = id; } /** Configure the vibrator capabilities with a combination of IVibrator.CAP_* values. */ @NonNull public Builder setCapabilities(long capabilities) { mCapabilities = capabilities; return this; } /** Configure the effects supported with {@link android.hardware.vibrator.Effect} values. */ @NonNull public Builder setSupportedEffects(int... supportedEffects) { mSupportedEffects = toSparseBooleanArray(supportedEffects); return this; } /** Configure braking supported with {@link android.hardware.vibrator.Braking} values. */ @NonNull public Builder setSupportedBraking(int... supportedBraking) { mSupportedBraking = toSparseBooleanArray(supportedBraking); return this; } /** Configure maximum duration, in milliseconds, of a PWLE primitive. */ @NonNull public Builder setPwlePrimitiveDurationMax(int pwlePrimitiveDurationMax) { mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax; return this; } /** Configure maximum number of primitives supported in a single PWLE composed effect. */ @NonNull public Builder setPwleSizeMax(int pwleSizeMax) { mPwleSizeMax = pwleSizeMax; return this; } /** Configure the duration of a {@link android.hardware.vibrator.CompositePrimitive}. */ @NonNull public Builder setSupportedPrimitive(int primitiveId, int duration) { mSupportedPrimitives.put(primitiveId, duration); return this; } /** Configure maximum delay, in milliseconds, supported in a composed effect primitive. */ @NonNull public Builder setPrimitiveDelayMax(int primitiveDelayMax) { mPrimitiveDelayMax = primitiveDelayMax; return this; } /** Configure maximum number of primitives supported in a single composed effect. */ @NonNull public Builder setCompositionSizeMax(int compositionSizeMax) { mCompositionSizeMax = compositionSizeMax; return this; } /** Configure the vibrator quality factor. */ @NonNull public Builder setQFactor(float qFactor) { mQFactor = qFactor; return this; } /** Configure the vibrator frequency information like resonant frequency and bandwidth. */ @NonNull public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) { mFrequencyProfile = frequencyProfile; return this; } /** Build the configured {@link VibratorInfo}. */ @NonNull public VibratorInfo build() { return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking, mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax, mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfile); } /** * Create a {@link SparseBooleanArray} from given {@code supportedKeys} where each key is * mapped * to {@code true}. */ @Nullable private static SparseBooleanArray toSparseBooleanArray(int[] supportedKeys) { if (supportedKeys == null) { return null; } SparseBooleanArray array = new SparseBooleanArray(); for (int key : supportedKeys) { array.put(key, true); } return array; } } @NonNull public static final Creator CREATOR = new Creator() { @Override public VibratorInfo createFromParcel(Parcel in) { return new VibratorInfo(in); } @Override public VibratorInfo[] newArray(int size) { return new VibratorInfo[size]; } }; }