• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.hardware.vibrator.Braking;
22 import android.hardware.vibrator.IVibrator;
23 import android.util.MathUtils;
24 import android.util.Range;
25 import android.util.SparseBooleanArray;
26 import android.util.SparseIntArray;
27 
28 import com.android.internal.util.Preconditions;
29 
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.Objects;
34 
35 /**
36  * A VibratorInfo describes the capabilities of a {@link Vibrator}.
37  *
38  * <p>This description includes its capabilities, list of supported effects and composition
39  * primitives.
40  *
41  * @hide
42  */
43 public class VibratorInfo implements Parcelable {
44     private static final String TAG = "VibratorInfo";
45 
46     /** @hide */
47     public static final VibratorInfo EMPTY_VIBRATOR_INFO = new VibratorInfo.Builder(-1).build();
48 
49     private final int mId;
50     private final long mCapabilities;
51     @Nullable
52     private final SparseBooleanArray mSupportedEffects;
53     @Nullable
54     private final SparseBooleanArray mSupportedBraking;
55     private final SparseIntArray mSupportedPrimitives;
56     private final int mPrimitiveDelayMax;
57     private final int mCompositionSizeMax;
58     private final int mPwlePrimitiveDurationMax;
59     private final int mPwleSizeMax;
60     private final float mQFactor;
61     private final FrequencyProfile mFrequencyProfile;
62 
VibratorInfo(Parcel in)63     VibratorInfo(Parcel in) {
64         mId = in.readInt();
65         mCapabilities = in.readLong();
66         mSupportedEffects = in.readSparseBooleanArray();
67         mSupportedBraking = in.readSparseBooleanArray();
68         mSupportedPrimitives = in.readSparseIntArray();
69         mPrimitiveDelayMax = in.readInt();
70         mCompositionSizeMax = in.readInt();
71         mPwlePrimitiveDurationMax = in.readInt();
72         mPwleSizeMax = in.readInt();
73         mQFactor = in.readFloat();
74         mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in);
75     }
76 
VibratorInfo(int id, @NonNull VibratorInfo baseVibratorInfo)77     public VibratorInfo(int id, @NonNull VibratorInfo baseVibratorInfo) {
78         this(id, baseVibratorInfo.mCapabilities, baseVibratorInfo.mSupportedEffects,
79                 baseVibratorInfo.mSupportedBraking, baseVibratorInfo.mSupportedPrimitives,
80                 baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax,
81                 baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax,
82                 baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfile);
83     }
84 
85     /**
86      * Default constructor.
87      *
88      * @param id                       The vibrator id.
89      * @param capabilities             All capability flags of the vibrator, defined in
90      *                                 IVibrator.CAP_*.
91      * @param supportedEffects         All supported predefined effects, enum values from
92      *                                 {@link android.hardware.vibrator.Effect}.
93      * @param supportedBraking         All supported braking types, enum values from {@link
94      *                                 Braking}.
95      * @param supportedPrimitives      All supported primitive effects, key are enum values from
96      *                                 {@link android.hardware.vibrator.CompositePrimitive} and
97      *                                 values are estimated durations in milliseconds.
98      * @param primitiveDelayMax        The maximum delay that can be set to a composition primitive
99      *                                 in milliseconds.
100      * @param compositionSizeMax       The maximum number of primitives supported by a composition.
101      * @param pwlePrimitiveDurationMax The maximum duration of a PWLE primitive in milliseconds.
102      * @param pwleSizeMax              The maximum number of primitives supported by a PWLE
103      *                                 composition.
104      * @param qFactor                  The vibrator quality factor.
105      * @param frequencyProfile         The description of the vibrator supported frequencies and max
106      *                                 amplitude mappings.
107      * @hide
108      */
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)109     public VibratorInfo(int id, long capabilities, @Nullable SparseBooleanArray supportedEffects,
110             @Nullable SparseBooleanArray supportedBraking,
111             @NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
112             int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
113             float qFactor, @NonNull FrequencyProfile frequencyProfile) {
114         Preconditions.checkNotNull(supportedPrimitives);
115         Preconditions.checkNotNull(frequencyProfile);
116         mId = id;
117         mCapabilities = capabilities;
118         mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
119         mSupportedBraking = supportedBraking == null ? null : supportedBraking.clone();
120         mSupportedPrimitives = supportedPrimitives.clone();
121         mPrimitiveDelayMax = primitiveDelayMax;
122         mCompositionSizeMax = compositionSizeMax;
123         mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
124         mPwleSizeMax = pwleSizeMax;
125         mQFactor = qFactor;
126         mFrequencyProfile = frequencyProfile;
127     }
128 
129     @Override
writeToParcel(Parcel dest, int flags)130     public void writeToParcel(Parcel dest, int flags) {
131         dest.writeInt(mId);
132         dest.writeLong(mCapabilities);
133         dest.writeSparseBooleanArray(mSupportedEffects);
134         dest.writeSparseBooleanArray(mSupportedBraking);
135         dest.writeSparseIntArray(mSupportedPrimitives);
136         dest.writeInt(mPrimitiveDelayMax);
137         dest.writeInt(mCompositionSizeMax);
138         dest.writeInt(mPwlePrimitiveDurationMax);
139         dest.writeInt(mPwleSizeMax);
140         dest.writeFloat(mQFactor);
141         mFrequencyProfile.writeToParcel(dest, flags);
142     }
143 
144     @Override
describeContents()145     public int describeContents() {
146         return 0;
147     }
148 
149     @Override
equals(Object o)150     public boolean equals(Object o) {
151         if (this == o) {
152             return true;
153         }
154         if (!(o instanceof VibratorInfo)) {
155             return false;
156         }
157         VibratorInfo that = (VibratorInfo) o;
158         int supportedPrimitivesCount = mSupportedPrimitives.size();
159         if (supportedPrimitivesCount != that.mSupportedPrimitives.size()) {
160             return false;
161         }
162         for (int i = 0; i < supportedPrimitivesCount; i++) {
163             if (mSupportedPrimitives.keyAt(i) != that.mSupportedPrimitives.keyAt(i)) {
164                 return false;
165             }
166             if (mSupportedPrimitives.valueAt(i) != that.mSupportedPrimitives.valueAt(i)) {
167                 return false;
168             }
169         }
170         return mId == that.mId && mCapabilities == that.mCapabilities
171                 && mPrimitiveDelayMax == that.mPrimitiveDelayMax
172                 && mCompositionSizeMax == that.mCompositionSizeMax
173                 && mPwlePrimitiveDurationMax == that.mPwlePrimitiveDurationMax
174                 && mPwleSizeMax == that.mPwleSizeMax
175                 && Objects.equals(mSupportedEffects, that.mSupportedEffects)
176                 && Objects.equals(mSupportedBraking, that.mSupportedBraking)
177                 && Objects.equals(mQFactor, that.mQFactor)
178                 && Objects.equals(mFrequencyProfile, that.mFrequencyProfile);
179     }
180 
181     @Override
hashCode()182     public int hashCode() {
183         int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
184                 mQFactor, mFrequencyProfile);
185         for (int i = 0; i < mSupportedPrimitives.size(); i++) {
186             hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
187             hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
188         }
189         return hashCode;
190     }
191 
192     @Override
toString()193     public String toString() {
194         return "VibratorInfo{"
195                 + "mId=" + mId
196                 + ", mCapabilities=" + Arrays.toString(getCapabilitiesNames())
197                 + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
198                 + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
199                 + ", mSupportedBraking=" + Arrays.toString(getSupportedBrakingNames())
200                 + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
201                 + ", mPrimitiveDelayMax=" + mPrimitiveDelayMax
202                 + ", mCompositionSizeMax=" + mCompositionSizeMax
203                 + ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax
204                 + ", mPwleSizeMax=" + mPwleSizeMax
205                 + ", mQFactor=" + mQFactor
206                 + ", mFrequencyProfile=" + mFrequencyProfile
207                 + '}';
208     }
209 
210     /** Return the id of this vibrator. */
getId()211     public int getId() {
212         return mId;
213     }
214 
215     /**
216      * Check whether the vibrator has amplitude control.
217      *
218      * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
219      */
hasAmplitudeControl()220     public boolean hasAmplitudeControl() {
221         return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
222     }
223 
224     /**
225      * Returns a default value to be applied to composed PWLE effects for braking.
226      *
227      * @return a supported braking value, one of android.hardware.vibrator.Braking.*
228      * @hide
229      */
getDefaultBraking()230     public int getDefaultBraking() {
231         if (mSupportedBraking != null) {
232             int size = mSupportedBraking.size();
233             for (int i = 0; i < size; i++) {
234                 if (mSupportedBraking.keyAt(i) != Braking.NONE) {
235                     return mSupportedBraking.keyAt(i);
236                 }
237             }
238         }
239         return Braking.NONE;
240     }
241 
242     /** @hide */
243     @Nullable
getSupportedBraking()244     public SparseBooleanArray getSupportedBraking() {
245         if (mSupportedBraking == null) {
246             return null;
247         }
248         return mSupportedBraking.clone();
249     }
250 
251     /** @hide */
isBrakingSupportKnown()252     public boolean isBrakingSupportKnown() {
253         return mSupportedBraking != null;
254     }
255 
256     /** @hide */
hasBrakingSupport(@raking int braking)257     public boolean hasBrakingSupport(@Braking int braking) {
258         return (mSupportedBraking != null) && mSupportedBraking.get(braking);
259     }
260 
261     /** @hide */
isEffectSupportKnown()262     public boolean isEffectSupportKnown() {
263         return mSupportedEffects != null;
264     }
265 
266     /**
267      * Query whether the vibrator supports the given effect.
268      *
269      * @param effectId Which effects to query for.
270      * @return {@link Vibrator#VIBRATION_EFFECT_SUPPORT_YES} if the effect is supported,
271      * {@link Vibrator#VIBRATION_EFFECT_SUPPORT_NO} if it isn't supported, or
272      * {@link Vibrator#VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether it's
273      * supported or not.
274      */
275     @Vibrator.VibrationEffectSupport
isEffectSupported(@ibrationEffect.EffectType int effectId)276     public int isEffectSupported(@VibrationEffect.EffectType int effectId) {
277         if (mSupportedEffects == null) {
278             return Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
279         }
280         return mSupportedEffects.get(effectId) ? Vibrator.VIBRATION_EFFECT_SUPPORT_YES
281                 : Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
282     }
283 
284     /** @hide */
285     @Nullable
getSupportedEffects()286     public SparseBooleanArray getSupportedEffects() {
287         if (mSupportedEffects == null) {
288             return null;
289         }
290         return mSupportedEffects.clone();
291     }
292 
293     /**
294      * Query whether the vibrator supports the given primitive.
295      *
296      * @param primitiveId Which primitives to query for.
297      * @return Whether the primitive is supported.
298      */
isPrimitiveSupported( @ibrationEffect.Composition.PrimitiveType int primitiveId)299     public boolean isPrimitiveSupported(
300             @VibrationEffect.Composition.PrimitiveType int primitiveId) {
301         return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)
302                 && (mSupportedPrimitives.indexOfKey(primitiveId) >= 0);
303     }
304 
305     /**
306      * Query the estimated duration of given primitive.
307      *
308      * @param primitiveId Which primitives to query for.
309      * @return The duration in milliseconds estimated for the primitive, or zero if primitive not
310      * supported.
311      */
getPrimitiveDuration( @ibrationEffect.Composition.PrimitiveType int primitiveId)312     public int getPrimitiveDuration(
313             @VibrationEffect.Composition.PrimitiveType int primitiveId) {
314         return mSupportedPrimitives.get(primitiveId);
315     }
316 
317     /** @hide */
getSupportedPrimitives()318     public SparseIntArray getSupportedPrimitives() {
319         return mSupportedPrimitives.clone();
320     }
321 
322     /**
323      * Query the maximum delay supported for a primitive in a composed effect.
324      *
325      * @return The max delay in milliseconds, or zero if unlimited.
326      */
getPrimitiveDelayMax()327     public int getPrimitiveDelayMax() {
328         return mPrimitiveDelayMax;
329     }
330 
331     /**
332      * Query the maximum number of primitives supported in a composed effect.
333      *
334      * @return The max number of primitives supported, or zero if unlimited.
335      */
getCompositionSizeMax()336     public int getCompositionSizeMax() {
337         return mCompositionSizeMax;
338     }
339 
340     /**
341      * Query the maximum duration supported for a primitive in a PWLE composition.
342      *
343      * @return The max duration in milliseconds, or zero if unlimited.
344      */
getPwlePrimitiveDurationMax()345     public int getPwlePrimitiveDurationMax() {
346         return mPwlePrimitiveDurationMax;
347     }
348 
349     /**
350      * Query the maximum number of primitives supported in a PWLE composition.
351      *
352      * @return The max number of primitives supported, or zero if unlimited.
353      */
getPwleSizeMax()354     public int getPwleSizeMax() {
355         return mPwleSizeMax;
356     }
357 
358     /**
359      * Check against this vibrator capabilities.
360      *
361      * @param capability one of IVibrator.CAP_*
362      * @return true if this vibrator has this capability, false otherwise
363      * @hide
364      */
hasCapability(long capability)365     public boolean hasCapability(long capability) {
366         return (mCapabilities & capability) == capability;
367     }
368 
369     /**
370      * Gets the resonant frequency of the vibrator.
371      *
372      * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
373      *         this vibrator is a composite of multiple physical devices.
374      */
getResonantFrequencyHz()375     public float getResonantFrequencyHz() {
376         return mFrequencyProfile.mResonantFrequencyHz;
377     }
378 
379     /**
380      * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
381      *
382      * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
383      *         this vibrator is a composite of multiple physical devices.
384      */
getQFactor()385     public float getQFactor() {
386         return mQFactor;
387     }
388 
389     /**
390      * Gets the profile of supported frequencies, including the measurements of maximum relative
391      * output acceleration for supported vibration frequencies.
392      *
393      * <p>If the devices does not have frequency control then the profile should be empty.
394      */
395     @NonNull
getFrequencyProfile()396     public FrequencyProfile getFrequencyProfile() {
397         return mFrequencyProfile;
398     }
399 
getCapabilities()400     protected long getCapabilities() {
401         return mCapabilities;
402     }
403 
getCapabilitiesNames()404     private String[] getCapabilitiesNames() {
405         List<String> names = new ArrayList<>();
406         if (hasCapability(IVibrator.CAP_ON_CALLBACK)) {
407             names.add("ON_CALLBACK");
408         }
409         if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) {
410             names.add("PERFORM_CALLBACK");
411         }
412         if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
413             names.add("COMPOSE_EFFECTS");
414         }
415         if (hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
416             names.add("COMPOSE_PWLE_EFFECTS");
417         }
418         if (hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
419             names.add("ALWAYS_ON_CONTROL");
420         }
421         if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
422             names.add("AMPLITUDE_CONTROL");
423         }
424         if (hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)) {
425             names.add("FREQUENCY_CONTROL");
426         }
427         if (hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
428             names.add("EXTERNAL_CONTROL");
429         }
430         if (hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
431             names.add("EXTERNAL_AMPLITUDE_CONTROL");
432         }
433         return names.toArray(new String[names.size()]);
434     }
435 
getSupportedEffectsNames()436     private String[] getSupportedEffectsNames() {
437         if (mSupportedEffects == null) {
438             return new String[0];
439         }
440         String[] names = new String[mSupportedEffects.size()];
441         for (int i = 0; i < mSupportedEffects.size(); i++) {
442             names[i] = VibrationEffect.effectIdToString(mSupportedEffects.keyAt(i));
443         }
444         return names;
445     }
446 
getSupportedBrakingNames()447     private String[] getSupportedBrakingNames() {
448         if (mSupportedBraking == null) {
449             return new String[0];
450         }
451         String[] names = new String[mSupportedBraking.size()];
452         for (int i = 0; i < mSupportedBraking.size(); i++) {
453             switch (mSupportedBraking.keyAt(i)) {
454                 case Braking.NONE:
455                     names[i] = "NONE";
456                     break;
457                 case Braking.CLAB:
458                     names[i] = "CLAB";
459                     break;
460                 default:
461                     names[i] = Integer.toString(mSupportedBraking.keyAt(i));
462             }
463         }
464         return names;
465     }
466 
getSupportedPrimitivesNames()467     private String[] getSupportedPrimitivesNames() {
468         int supportedPrimitivesCount = mSupportedPrimitives.size();
469         String[] names = new String[supportedPrimitivesCount];
470         for (int i = 0; i < supportedPrimitivesCount; i++) {
471             names[i] = VibrationEffect.Composition.primitiveToString(mSupportedPrimitives.keyAt(i))
472                     + "(" + mSupportedPrimitives.valueAt(i) + "ms)";
473         }
474         return names;
475     }
476 
477     /**
478      * Describes the maximum relative output acceleration that can be achieved for each supported
479      * frequency in a specific vibrator.
480      *
481      * <p>This profile is defined by the following parameters:
482      *
483      * <ol>
484      *     <li>{@code minFrequencyHz}, {@code resonantFrequencyHz} and {@code frequencyResolutionHz}
485      *         provided by the vibrator in hertz.
486      *     <li>{@code maxAmplitudes} a list of values in [0,1] provided by the vibrator, where
487      *         {@code maxAmplitudes[i]} represents max supported amplitude at frequency
488      *         {@code minFrequencyHz + frequencyResolutionHz * i}.
489      *     <li>{@code maxFrequencyHz = minFrequencyHz
490      *                                     + frequencyResolutionHz * (maxAmplitudes.length-1)}
491      * </ol>
492      *
493      * @hide
494      */
495     public static final class FrequencyProfile implements Parcelable {
496         @Nullable
497         private final Range<Float> mFrequencyRangeHz;
498         private final float mMinFrequencyHz;
499         private final float mResonantFrequencyHz;
500         private final float mFrequencyResolutionHz;
501         private final float[] mMaxAmplitudes;
502 
FrequencyProfile(Parcel in)503         FrequencyProfile(Parcel in) {
504             this(in.readFloat(), in.readFloat(), in.readFloat(), in.createFloatArray());
505         }
506 
507         /**
508          * Default constructor.
509          *
510          * @param resonantFrequencyHz   The vibrator resonant frequency, in hertz.
511          * @param minFrequencyHz        Minimum supported frequency, in hertz.
512          * @param frequencyResolutionHz The frequency resolution, in hertz, used by the max
513          *                              amplitude measurements.
514          * @param maxAmplitudes         The max amplitude supported by each supported frequency,
515          *                              starting at minimum frequency with jumps of frequency
516          *                              resolution.
517          * @hide
518          */
FrequencyProfile(float resonantFrequencyHz, float minFrequencyHz, float frequencyResolutionHz, float[] maxAmplitudes)519         public FrequencyProfile(float resonantFrequencyHz, float minFrequencyHz,
520                 float frequencyResolutionHz, float[] maxAmplitudes) {
521             mMinFrequencyHz = minFrequencyHz;
522             mResonantFrequencyHz = resonantFrequencyHz;
523             mFrequencyResolutionHz = frequencyResolutionHz;
524             mMaxAmplitudes = new float[maxAmplitudes == null ? 0 : maxAmplitudes.length];
525             if (maxAmplitudes != null) {
526                 System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length);
527             }
528 
529             // If any required field is undefined or has a bad value then this profile is invalid.
530             boolean isValid = !Float.isNaN(resonantFrequencyHz)
531                     && (resonantFrequencyHz > 0)
532                     && !Float.isNaN(minFrequencyHz)
533                     && (minFrequencyHz > 0)
534                     && !Float.isNaN(frequencyResolutionHz)
535                     && (frequencyResolutionHz > 0)
536                     && (mMaxAmplitudes.length > 0);
537 
538             // If any max amplitude is outside the allowed range then this profile is invalid.
539             for (int i = 0; i < mMaxAmplitudes.length; i++) {
540                 isValid &= (mMaxAmplitudes[i] >= 0) && (mMaxAmplitudes[i] <= 1);
541             }
542 
543             float maxFrequencyHz = isValid
544                     ? minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1)
545                     : Float.NaN;
546 
547             // If the constraint min < resonant < max is not met then it is invalid.
548             isValid &= !Float.isNaN(maxFrequencyHz)
549                     && (resonantFrequencyHz >= minFrequencyHz)
550                     && (resonantFrequencyHz <= maxFrequencyHz)
551                     && (minFrequencyHz < maxFrequencyHz);
552 
553             mFrequencyRangeHz = isValid ? Range.create(minFrequencyHz, maxFrequencyHz) : null;
554         }
555 
556         /** Returns true if the supported frequency range is empty. */
isEmpty()557         public boolean isEmpty() {
558             return mFrequencyRangeHz == null;
559         }
560 
561         /** Returns the supported frequency range, in hertz. */
562         @Nullable
getFrequencyRangeHz()563         public Range<Float> getFrequencyRangeHz() {
564             return mFrequencyRangeHz;
565         }
566 
567         /**
568          * Returns the maximum relative amplitude the vibrator can reach while playing at the
569          * given frequency.
570          *
571          * @param frequencyHz frequency, in hertz, for query.
572          * @return A value in [0,1] representing the max relative amplitude supported at the given
573          * frequency. This will return 0 if the frequency is outside the supported range, or if the
574          * supported frequency range is empty.
575          */
getMaxAmplitude(float frequencyHz)576         public float getMaxAmplitude(float frequencyHz) {
577             if (isEmpty() || Float.isNaN(frequencyHz) || !mFrequencyRangeHz.contains(frequencyHz)) {
578                 // Unsupported frequency requested, vibrator cannot play at this frequency.
579                 return 0;
580             }
581 
582             // Subtract minFrequencyHz to simplify offset calculations.
583             float mappingFreq = frequencyHz - mMinFrequencyHz;
584 
585             // Find the bucket to interpolate within.
586             // Any calculated index should be safe, except exactly equal to max amplitude can be
587             // one step too high, so constrain it to guarantee safety.
588             int startIdx = MathUtils.constrain(
589                     /* amount= */ (int) Math.floor(mappingFreq / mFrequencyResolutionHz),
590                     /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
591             int nextIdx = MathUtils.constrain(
592                     /* amount= */ startIdx + 1,
593                     /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
594 
595             // Linearly interpolate the amplitudes based on the frequency range of the bucket.
596             return MathUtils.constrainedMap(
597                     mMaxAmplitudes[startIdx], mMaxAmplitudes[nextIdx],
598                     startIdx * mFrequencyResolutionHz, nextIdx * mFrequencyResolutionHz,
599                     mappingFreq);
600         }
601 
602         /** Returns the raw list of maximum relative output accelerations from the vibrator. */
603         @NonNull
getMaxAmplitudes()604         public float[] getMaxAmplitudes() {
605             return Arrays.copyOf(mMaxAmplitudes, mMaxAmplitudes.length);
606         }
607 
608         /** Returns the raw frequency resolution used for max amplitude measurements, in hertz. */
getFrequencyResolutionHz()609         public float getFrequencyResolutionHz() {
610             return mFrequencyResolutionHz;
611         }
612 
613         @Override
writeToParcel(Parcel dest, int flags)614         public void writeToParcel(Parcel dest, int flags) {
615             dest.writeFloat(mResonantFrequencyHz);
616             dest.writeFloat(mMinFrequencyHz);
617             dest.writeFloat(mFrequencyResolutionHz);
618             dest.writeFloatArray(mMaxAmplitudes);
619         }
620 
621         @Override
describeContents()622         public int describeContents() {
623             return 0;
624         }
625 
626         @Override
equals(Object o)627         public boolean equals(Object o) {
628             if (this == o) {
629                 return true;
630             }
631             if (!(o instanceof FrequencyProfile)) {
632                 return false;
633             }
634             FrequencyProfile that = (FrequencyProfile) o;
635             return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0
636                     && Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
637                     && Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0
638                     && Arrays.equals(mMaxAmplitudes, that.mMaxAmplitudes);
639         }
640 
641         @Override
hashCode()642         public int hashCode() {
643             int hashCode = Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz,
644                     mFrequencyResolutionHz);
645             hashCode = 31 * hashCode + Arrays.hashCode(mMaxAmplitudes);
646             return hashCode;
647         }
648 
649         @Override
toString()650         public String toString() {
651             return "FrequencyProfile{"
652                     + "mFrequencyRange=" + mFrequencyRangeHz
653                     + ", mMinFrequency=" + mMinFrequencyHz
654                     + ", mResonantFrequency=" + mResonantFrequencyHz
655                     + ", mFrequencyResolution=" + mFrequencyResolutionHz
656                     + ", mMaxAmplitudes count=" + mMaxAmplitudes.length
657                     + '}';
658         }
659 
660         @NonNull
661         public static final Creator<FrequencyProfile> CREATOR =
662                 new Creator<FrequencyProfile>() {
663                     @Override
664                     public FrequencyProfile createFromParcel(Parcel in) {
665                         return new FrequencyProfile(in);
666                     }
667 
668                     @Override
669                     public FrequencyProfile[] newArray(int size) {
670                         return new FrequencyProfile[size];
671                     }
672                 };
673     }
674 
675     /** @hide */
676     public static final class Builder {
677         private final int mId;
678         private long mCapabilities;
679         private SparseBooleanArray mSupportedEffects;
680         private SparseBooleanArray mSupportedBraking;
681         private SparseIntArray mSupportedPrimitives = new SparseIntArray();
682         private int mPrimitiveDelayMax;
683         private int mCompositionSizeMax;
684         private int mPwlePrimitiveDurationMax;
685         private int mPwleSizeMax;
686         private float mQFactor = Float.NaN;
687         private FrequencyProfile mFrequencyProfile =
688                 new FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
689 
690         /** A builder class for a {@link VibratorInfo}. */
Builder(int id)691         public Builder(int id) {
692             mId = id;
693         }
694 
695         /** Configure the vibrator capabilities with a combination of IVibrator.CAP_* values. */
696         @NonNull
setCapabilities(long capabilities)697         public Builder setCapabilities(long capabilities) {
698             mCapabilities = capabilities;
699             return this;
700         }
701 
702         /** Configure the effects supported with {@link android.hardware.vibrator.Effect} values. */
703         @NonNull
setSupportedEffects(int... supportedEffects)704         public Builder setSupportedEffects(int... supportedEffects) {
705             mSupportedEffects = toSparseBooleanArray(supportedEffects);
706             return this;
707         }
708 
709         /** Configure braking supported with {@link android.hardware.vibrator.Braking} values. */
710         @NonNull
setSupportedBraking(int... supportedBraking)711         public Builder setSupportedBraking(int... supportedBraking) {
712             mSupportedBraking = toSparseBooleanArray(supportedBraking);
713             return this;
714         }
715 
716         /** Configure maximum duration, in milliseconds, of a PWLE primitive. */
717         @NonNull
setPwlePrimitiveDurationMax(int pwlePrimitiveDurationMax)718         public Builder setPwlePrimitiveDurationMax(int pwlePrimitiveDurationMax) {
719             mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
720             return this;
721         }
722 
723         /** Configure maximum number of primitives supported in a single PWLE composed effect. */
724         @NonNull
setPwleSizeMax(int pwleSizeMax)725         public Builder setPwleSizeMax(int pwleSizeMax) {
726             mPwleSizeMax = pwleSizeMax;
727             return this;
728         }
729 
730         /** Configure the duration of a {@link android.hardware.vibrator.CompositePrimitive}. */
731         @NonNull
setSupportedPrimitive(int primitiveId, int duration)732         public Builder setSupportedPrimitive(int primitiveId, int duration) {
733             mSupportedPrimitives.put(primitiveId, duration);
734             return this;
735         }
736 
737         /** Configure maximum delay, in milliseconds, supported in a composed effect primitive. */
738         @NonNull
setPrimitiveDelayMax(int primitiveDelayMax)739         public Builder setPrimitiveDelayMax(int primitiveDelayMax) {
740             mPrimitiveDelayMax = primitiveDelayMax;
741             return this;
742         }
743 
744         /** Configure maximum number of primitives supported in a single composed effect. */
745         @NonNull
setCompositionSizeMax(int compositionSizeMax)746         public Builder setCompositionSizeMax(int compositionSizeMax) {
747             mCompositionSizeMax = compositionSizeMax;
748             return this;
749         }
750 
751         /** Configure the vibrator quality factor. */
752         @NonNull
setQFactor(float qFactor)753         public Builder setQFactor(float qFactor) {
754             mQFactor = qFactor;
755             return this;
756         }
757 
758         /** Configure the vibrator frequency information like resonant frequency and bandwidth. */
759         @NonNull
setFrequencyProfile(@onNull FrequencyProfile frequencyProfile)760         public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) {
761             mFrequencyProfile = frequencyProfile;
762             return this;
763         }
764 
765         /** Build the configured {@link VibratorInfo}. */
766         @NonNull
build()767         public VibratorInfo build() {
768             return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
769                     mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax,
770                     mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfile);
771         }
772 
773         /**
774          * Create a {@link SparseBooleanArray} from given {@code supportedKeys} where each key is
775          * mapped
776          * to {@code true}.
777          */
778         @Nullable
toSparseBooleanArray(int[] supportedKeys)779         private static SparseBooleanArray toSparseBooleanArray(int[] supportedKeys) {
780             if (supportedKeys == null) {
781                 return null;
782             }
783             SparseBooleanArray array = new SparseBooleanArray();
784             for (int key : supportedKeys) {
785                 array.put(key, true);
786             }
787             return array;
788         }
789     }
790 
791     @NonNull
792     public static final Creator<VibratorInfo> CREATOR =
793             new Creator<VibratorInfo>() {
794                 @Override
795                 public VibratorInfo createFromParcel(Parcel in) {
796                     return new VibratorInfo(in);
797                 }
798 
799                 @Override
800                 public VibratorInfo[] newArray(int size) {
801                     return new VibratorInfo[size];
802                 }
803             };
804 }
805