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