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