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