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