1 /* 2 * Copyright (C) 2017 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 static android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS; 20 21 import android.annotation.DurationMillisLong; 22 import android.annotation.FlaggedApi; 23 import android.annotation.FloatRange; 24 import android.annotation.IntDef; 25 import android.annotation.IntRange; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.RequiresPermission; 29 import android.annotation.SystemApi; 30 import android.annotation.TestApi; 31 import android.compat.annotation.UnsupportedAppUsage; 32 import android.content.ContentResolver; 33 import android.content.Context; 34 import android.hardware.vibrator.IVibrator; 35 import android.hardware.vibrator.V1_0.EffectStrength; 36 import android.hardware.vibrator.V1_3.Effect; 37 import android.net.Uri; 38 import android.os.vibrator.BasicPwleSegment; 39 import android.os.vibrator.Flags; 40 import android.os.vibrator.PrebakedSegment; 41 import android.os.vibrator.PrimitiveSegment; 42 import android.os.vibrator.PwleSegment; 43 import android.os.vibrator.RampSegment; 44 import android.os.vibrator.StepSegment; 45 import android.os.vibrator.VibrationEffectSegment; 46 import android.os.vibrator.VibratorEnvelopeEffectInfo; 47 import android.os.vibrator.VibratorFrequencyProfileLegacy; 48 import android.util.MathUtils; 49 50 import com.android.internal.util.Preconditions; 51 52 import java.lang.annotation.Retention; 53 import java.lang.annotation.RetentionPolicy; 54 import java.time.Duration; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.List; 58 import java.util.Locale; 59 import java.util.Objects; 60 import java.util.StringJoiner; 61 import java.util.function.BiFunction; 62 import java.util.function.Function; 63 64 /** 65 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}. 66 * 67 * <p>These effects may be any number of things, from single shot vibrations to complex waveforms. 68 */ 69 public abstract class VibrationEffect implements Parcelable { 70 private static final int PARCEL_TOKEN_COMPOSED = 1; 71 private static final int PARCEL_TOKEN_VENDOR_EFFECT = 2; 72 73 // Stevens' coefficient to scale the perceived vibration intensity. 74 private static final float SCALE_GAMMA = 0.65f; 75 // If a vibration is playing for longer than 1s, it's probably not haptic feedback 76 private static final long MAX_HAPTIC_FEEDBACK_DURATION = 1000; 77 // If a vibration is playing more than 3 constants, it's probably not haptic feedback 78 private static final long MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE = 3; 79 80 /** 81 * The default vibration strength of the device. 82 */ 83 public static final int DEFAULT_AMPLITUDE = -1; 84 85 /** 86 * The maximum amplitude value 87 * @hide 88 */ 89 public static final int MAX_AMPLITUDE = 255; 90 91 /** 92 * A click effect. Use this effect as a baseline, as it's the most common type of click effect. 93 */ 94 public static final int EFFECT_CLICK = Effect.CLICK; 95 96 /** 97 * A double click effect. 98 */ 99 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK; 100 101 /** 102 * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}. 103 */ 104 public static final int EFFECT_TICK = Effect.TICK; 105 106 /** 107 * A thud effect. 108 * @see #get(int) 109 * @hide 110 */ 111 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 112 @TestApi 113 public static final int EFFECT_THUD = Effect.THUD; 114 115 /** 116 * A pop effect. 117 * @see #get(int) 118 * @hide 119 */ 120 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 121 @TestApi 122 public static final int EFFECT_POP = Effect.POP; 123 124 /** 125 * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}. 126 */ 127 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK; 128 129 /** 130 * A texture effect meant to replicate soft ticks. 131 * 132 * <p>Unlike normal effects, texture effects are meant to be called repeatedly, generally in 133 * response to some motion, in order to replicate the feeling of some texture underneath the 134 * user's fingers. 135 * 136 * @see #get(int) 137 * @hide 138 */ 139 @TestApi 140 public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK; 141 142 /** {@hide} */ 143 @TestApi 144 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT; 145 146 /** {@hide} */ 147 @TestApi 148 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM; 149 150 /** {@hide} */ 151 @TestApi 152 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG; 153 154 /** 155 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a 156 * pattern that can be played as a ringtone with any audio, depending on the device. 157 * 158 * @see #get(Uri, Context) 159 * @hide 160 */ 161 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 162 @TestApi 163 public static final int[] RINGTONES = { 164 Effect.RINGTONE_1, 165 Effect.RINGTONE_2, 166 Effect.RINGTONE_3, 167 Effect.RINGTONE_4, 168 Effect.RINGTONE_5, 169 Effect.RINGTONE_6, 170 Effect.RINGTONE_7, 171 Effect.RINGTONE_8, 172 Effect.RINGTONE_9, 173 Effect.RINGTONE_10, 174 Effect.RINGTONE_11, 175 Effect.RINGTONE_12, 176 Effect.RINGTONE_13, 177 Effect.RINGTONE_14, 178 Effect.RINGTONE_15 179 }; 180 181 /** @hide */ 182 @IntDef(prefix = { "EFFECT_" }, value = { 183 EFFECT_TICK, 184 EFFECT_CLICK, 185 EFFECT_HEAVY_CLICK, 186 EFFECT_DOUBLE_CLICK, 187 }) 188 @Retention(RetentionPolicy.SOURCE) 189 public @interface EffectType {} 190 191 /** @hide to prevent subclassing from outside of the framework */ VibrationEffect()192 public VibrationEffect() { } 193 194 /** 195 * Create a one shot vibration. 196 * 197 * <p>One shot vibrations will vibrate constantly for the specified period of time at the 198 * specified amplitude, and then stop. 199 * 200 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number. 201 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or 202 * {@link #DEFAULT_AMPLITUDE}. 203 * 204 * @return The desired effect. 205 */ createOneShot(long milliseconds, int amplitude)206 public static VibrationEffect createOneShot(long milliseconds, int amplitude) { 207 if (amplitude == 0) { 208 throw new IllegalArgumentException( 209 "amplitude must either be DEFAULT_AMPLITUDE, " 210 + "or between 1 and 255 inclusive (amplitude=" + amplitude + ")"); 211 } 212 return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */); 213 } 214 215 /** 216 * Create a waveform vibration, using only off/on transitions at the provided time intervals, 217 * and potentially repeating. 218 * 219 * <p>In effect, the timings array represents the number of milliseconds <em>before</em> turning 220 * the vibrator on, followed by the number of milliseconds to keep the vibrator on, then 221 * the number of milliseconds turned off, and so on. Consequently, the first timing value will 222 * often be 0, so that the effect will start vibrating immediately. 223 * 224 * <p>This method is equivalent to calling {@link #createWaveform(long[], int[], int)} with 225 * corresponding amplitude values alternating between 0 and {@link #DEFAULT_AMPLITUDE}, 226 * beginning with 0. 227 * 228 * <p>To cause the pattern to repeat, pass the index into the timings array at which to start 229 * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely 230 * and should be cancelled via {@link Vibrator#cancel()}. 231 * 232 * @param timings The pattern of alternating on-off timings, starting with an 'off' timing, and 233 * representing the length of time to sustain the individual item (not 234 * cumulative). 235 * @param repeat The index into the timings array at which to repeat, or -1 if you don't 236 * want to repeat indefinitely. 237 * 238 * @return The desired effect. 239 */ createWaveform(long[] timings, int repeat)240 public static VibrationEffect createWaveform(long[] timings, int repeat) { 241 int[] amplitudes = new int[timings.length]; 242 for (int i = 0; i < (timings.length / 2); i++) { 243 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE; 244 } 245 return createWaveform(timings, amplitudes, repeat); 246 } 247 248 /** 249 * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on" 250 * vibration components) that is equivalent to this VibrationEffect. 251 * 252 * <p>All non-repeating effects created with {@link #createWaveform(long[], int)} are 253 * convertible into an equivalent vibration pattern with this method. It is not guaranteed that 254 * an effect created with other means becomes converted into an equivalent legacy vibration 255 * pattern, even if it has an equivalent vibration pattern. If this method is unable to create 256 * an equivalent vibration pattern for such effects, it will return {@code null}. 257 * 258 * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any 259 * form of repeating behavior, regardless of how the effect was created. For repeating effects, 260 * the method will always return {@code null}. 261 * 262 * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if 263 * the method successfully derived a vibration pattern equivalent to the effect 264 * (this will always be the case if the effect was created via 265 * {@link #createWaveform(long[], int)} and is non-repeating). Otherwise, returns 266 * {@code null}. 267 * @hide 268 */ 269 @TestApi 270 @Nullable computeCreateWaveformOffOnTimingsOrNull()271 public abstract long[] computeCreateWaveformOffOnTimingsOrNull(); 272 273 /** 274 * Create a waveform vibration. 275 * 276 * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs, 277 * provided in separate arrays. For each pair, the value in the amplitude array determines 278 * the strength of the vibration and the value in the timing array determines how long it 279 * vibrates for, in milliseconds. 280 * 281 * <p>To cause the pattern to repeat, pass the index into the timings array at which to start 282 * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely 283 * and should be cancelled via {@link Vibrator#cancel()}. 284 * 285 * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing 286 * values of 0 will cause the pair to be ignored. 287 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values 288 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An 289 * amplitude value of 0 implies the motor is off. 290 * @param repeat The index into the timings array at which to repeat, or -1 if you don't 291 * want to repeat indefinitely. 292 * 293 * @return The desired effect. 294 */ createWaveform(long[] timings, int[] amplitudes, int repeat)295 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { 296 if (timings.length != amplitudes.length) { 297 throw new IllegalArgumentException( 298 "timing and amplitude arrays must be of equal length" 299 + " (timings.length=" + timings.length 300 + ", amplitudes.length=" + amplitudes.length + ")"); 301 } 302 List<StepSegment> segments = new ArrayList<>(); 303 for (int i = 0; i < timings.length; i++) { 304 float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE 305 ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE; 306 segments.add(new StepSegment(parsedAmplitude, /* frequencyHz= */ 0, (int) timings[i])); 307 } 308 VibrationEffect effect = new Composed(segments, repeat); 309 effect.validate(); 310 return effect; 311 } 312 313 /** 314 * Create a predefined vibration effect. 315 * 316 * <p>Predefined effects are a set of common vibration effects that should be identical, 317 * regardless of the app they come from, in order to provide a cohesive experience for users 318 * across the entire device. They also may be custom tailored to the device hardware in order to 319 * provide a better experience than you could otherwise build using the generic building 320 * blocks. 321 * 322 * <p>This will fallback to a generic pattern if one exists and there does not exist a 323 * hardware-specific implementation of the effect. 324 * 325 * @param effectId The ID of the effect to perform: 326 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 327 * 328 * @return The desired effect. 329 */ 330 @NonNull createPredefined(@ffectType int effectId)331 public static VibrationEffect createPredefined(@EffectType int effectId) { 332 return get(effectId, true); 333 } 334 335 /** 336 * Create a vendor-defined vibration effect. 337 * 338 * <p>Vendor effects offer more flexibility for accessing vendor-specific vibrator capabilities, 339 * enabling control over any vibration parameter and more generic vibration waveforms for apps 340 * provided by the device vendor. 341 * 342 * <p>This requires hardware-specific implementation of the effect and will not have any 343 * platform fallback support. 344 * 345 * @param effect An opaque representation of the vibration effect which can also be serialized. 346 * @return The desired effect. 347 * @hide 348 */ 349 @NonNull 350 @SystemApi 351 @FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS) 352 @RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS) createVendorEffect(@onNull PersistableBundle effect)353 public static VibrationEffect createVendorEffect(@NonNull PersistableBundle effect) { 354 VibrationEffect vendorEffect = new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH, 355 VendorEffect.DEFAULT_SCALE, VendorEffect.DEFAULT_SCALE); 356 vendorEffect.validate(); 357 return vendorEffect; 358 } 359 360 /** 361 * Get a predefined vibration effect. 362 * 363 * <p>Predefined effects are a set of common vibration effects that should be identical, 364 * regardless of the app they come from, in order to provide a cohesive experience for users 365 * across the entire device. They also may be custom tailored to the device hardware in order to 366 * provide a better experience than you could otherwise build using the generic building 367 * blocks. 368 * 369 * <p>This will fallback to a generic pattern if one exists and there does not exist a 370 * hardware-specific implementation of the effect. 371 * 372 * @param effectId The ID of the effect to perform: 373 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 374 * 375 * @return The desired effect. 376 * @hide 377 */ 378 @TestApi get(int effectId)379 public static VibrationEffect get(int effectId) { 380 return get(effectId, PrebakedSegment.DEFAULT_SHOULD_FALLBACK); 381 } 382 383 /** 384 * Get a predefined vibration effect. 385 * 386 * <p>Predefined effects are a set of common vibration effects that should be identical, 387 * regardless of the app they come from, in order to provide a cohesive experience for users 388 * across the entire device. They also may be custom tailored to the device hardware in order to 389 * provide a better experience than you could otherwise build using the generic building 390 * blocks. 391 * 392 * <p>Some effects you may only want to play if there's a hardware specific implementation 393 * because they may, for example, be too disruptive to the user without tuning. The 394 * {@code fallback} parameter allows you to decide whether you want to fallback to the generic 395 * implementation or only play if there's a tuned, hardware specific one available. 396 * 397 * @param effectId The ID of the effect to perform: 398 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 399 * @param fallback Whether to fall back to a generic pattern if a hardware specific 400 * implementation doesn't exist. 401 * 402 * @return The desired effect. 403 * @hide 404 */ 405 @TestApi get(int effectId, boolean fallback)406 public static VibrationEffect get(int effectId, boolean fallback) { 407 VibrationEffect effect = new Composed( 408 new PrebakedSegment(effectId, fallback, PrebakedSegment.DEFAULT_STRENGTH)); 409 effect.validate(); 410 return effect; 411 } 412 413 /** 414 * Get a predefined vibration effect associated with a given URI. 415 * 416 * <p>Predefined effects are a set of common vibration effects that should be identical, 417 * regardless of the app they come from, in order to provide a cohesive experience for users 418 * across the entire device. They also may be custom tailored to the device hardware in order to 419 * provide a better experience than you could otherwise build using the generic building 420 * blocks. 421 * 422 * @param uri The URI associated with the haptic effect. 423 * @param context The context used to get the URI to haptic effect association. 424 * 425 * @return The desired effect, or {@code null} if there's no associated effect. 426 * 427 * @hide 428 */ 429 @TestApi 430 @Nullable get(Uri uri, Context context)431 public static VibrationEffect get(Uri uri, Context context) { 432 String[] uris = context.getResources().getStringArray( 433 com.android.internal.R.array.config_ringtoneEffectUris); 434 435 // Skip doing any IPC if we don't have any effects configured. 436 if (uris.length == 0) { 437 return null; 438 } 439 440 final ContentResolver cr = context.getContentResolver(); 441 Uri uncanonicalUri = cr.uncanonicalize(uri); 442 if (uncanonicalUri == null) { 443 // If we already had an uncanonical URI, it's possible we'll get null back here. In 444 // this case, just use the URI as passed in since it wasn't canonicalized in the first 445 // place. 446 uncanonicalUri = uri; 447 } 448 449 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) { 450 if (uris[i] == null) { 451 continue; 452 } 453 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i])); 454 if (mappedUri == null) { 455 continue; 456 } 457 if (mappedUri.equals(uncanonicalUri)) { 458 return get(RINGTONES[i]); 459 } 460 } 461 return null; 462 } 463 464 /** 465 * Start composing a haptic effect. 466 * 467 * @see VibrationEffect.Composition 468 */ 469 @NonNull startComposition()470 public static Composition startComposition() { 471 return new Composition(); 472 } 473 474 /** 475 * Start building a waveform vibration. 476 * 477 * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing 478 * control over vibration amplitude and frequency via smooth transitions between values. 479 * 480 * <p>The waveform will start the first transition from the vibrator off state, with the 481 * resonant frequency by default. To provide an initial state, use 482 * {@link #startWaveform(VibrationEffect.VibrationParameter)}. 483 * 484 * @see VibrationEffect.WaveformBuilder 485 * @hide 486 */ 487 @TestApi 488 @NonNull startWaveform()489 public static WaveformBuilder startWaveform() { 490 return new WaveformBuilder(); 491 } 492 493 /** 494 * Start building a waveform vibration with an initial state specified by a 495 * {@link VibrationEffect.VibrationParameter}. 496 * 497 * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing 498 * control over vibration amplitude and frequency via smooth transitions between values. 499 * 500 * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be 501 * applied at the beginning of the vibration. 502 * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. 503 * 504 * @see VibrationEffect.WaveformBuilder 505 * @hide 506 */ 507 @TestApi 508 @NonNull startWaveform(@onNull VibrationParameter initialParameter)509 public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) { 510 WaveformBuilder builder = startWaveform(); 511 builder.addTransition(Duration.ZERO, initialParameter); 512 return builder; 513 } 514 515 /** 516 * Start building a waveform vibration with an initial state specified by two 517 * {@link VibrationEffect.VibrationParameter VibrationParameters}. 518 * 519 * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing 520 * control over vibration amplitude and frequency via smooth transitions between values. 521 * 522 * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be 523 * applied at the beginning of the vibration. 524 * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be 525 * applied at the beginning of the vibration, must be a different type 526 * of parameter than the one specified by the first argument. 527 * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. 528 * 529 * @see VibrationEffect.WaveformBuilder 530 * @hide 531 */ 532 @TestApi 533 @NonNull startWaveform(@onNull VibrationParameter initialParameter1, @NonNull VibrationParameter initialParameter2)534 public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1, 535 @NonNull VibrationParameter initialParameter2) { 536 WaveformBuilder builder = startWaveform(); 537 builder.addTransition(Duration.ZERO, initialParameter1, initialParameter2); 538 return builder; 539 } 540 541 @Override describeContents()542 public int describeContents() { 543 return 0; 544 } 545 546 /** @hide */ validate()547 public abstract void validate(); 548 549 550 /** 551 * If supported, truncate the length of this vibration effect to the provided length and return 552 * the result. Will always return null for repeating effects. 553 * 554 * @return The desired effect, or {@code null} if truncation is not applicable. 555 * @hide 556 */ 557 @Nullable cropToLengthOrNull(int length)558 public abstract VibrationEffect cropToLengthOrNull(int length); 559 560 /** 561 * Gets the estimated duration of the vibration in milliseconds. 562 * 563 * <p>For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this 564 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. predefined effects where 565 * the length is device and potentially run-time dependent), this returns -1. 566 * 567 * @hide 568 */ 569 @TestApi getDuration()570 public abstract long getDuration(); 571 572 /** 573 * Gets the estimated duration of the segment for given vibrator, in milliseconds. 574 * 575 * <p>For effects with hardware-dependent constants (e.g. primitive compositions), this returns 576 * the estimated duration based on the given {@link VibratorInfo}. For all other effects this 577 * will return the same as {@link #getDuration()}. 578 * 579 * @hide 580 */ getDuration(@ullable VibratorInfo vibratorInfo)581 public long getDuration(@Nullable VibratorInfo vibratorInfo) { 582 return getDuration(); 583 } 584 585 /** 586 * Checks if a vibrator with a given {@link VibratorInfo} can play this effect as intended. 587 * 588 * <p>See {@link VibratorInfo#areVibrationFeaturesSupported(VibrationEffect)} for more 589 * information about what counts as supported by a vibrator, and what counts as not. 590 * 591 * @hide 592 */ areVibrationFeaturesSupported(@onNull VibratorInfo vibratorInfo)593 public abstract boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo); 594 595 /** 596 * Returns true if this effect could represent a touch haptic feedback. 597 * 598 * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified 599 * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN, 600 * then this method will be used to classify the most common use case and make sure they are 601 * covered by the user settings for "Touch feedback". 602 * 603 * @hide 604 */ isHapticFeedbackCandidate()605 public boolean isHapticFeedbackCandidate() { 606 return false; 607 } 608 609 /** 610 * Resolve default values into integer amplitude numbers. 611 * 612 * @param defaultAmplitude the default amplitude to apply, must be between 0 and 613 * MAX_AMPLITUDE 614 * @return this if amplitude value is already set, or a copy of this effect with given default 615 * amplitude otherwise 616 * 617 * @hide 618 */ 619 @NonNull resolve(int defaultAmplitude)620 public abstract VibrationEffect resolve(int defaultAmplitude); 621 622 /** 623 * Applies given effect strength to predefined and vendor-specific effects. 624 * 625 * @param effectStrength new effect strength to be applied, one of 626 * VibrationEffect.EFFECT_STRENGTH_*. 627 * @return this if there is no change, or a copy of this effect with new strength otherwise 628 * @hide 629 */ 630 @NonNull applyEffectStrength(int effectStrength)631 public abstract VibrationEffect applyEffectStrength(int effectStrength); 632 633 /** 634 * Scale the vibration effect intensity with the given constraints. 635 * 636 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will 637 * scale down the intensity, values larger than 1 will scale up 638 * @return this if there is no scaling to be done, or a copy of this effect with scaled 639 * vibration intensity otherwise 640 * 641 * @hide 642 */ 643 @NonNull scale(float scaleFactor)644 public abstract VibrationEffect scale(float scaleFactor); 645 646 /** 647 * Performs a linear scaling on the effect intensity with the given factor. 648 * 649 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will 650 * scale down the intensity, values larger than 1 will scale up 651 * @return this if there is no scaling to be done, or a copy of this effect with scaled 652 * vibration intensity otherwise 653 * @hide 654 */ 655 @NonNull applyAdaptiveScale(float scaleFactor)656 public abstract VibrationEffect applyAdaptiveScale(float scaleFactor); 657 658 /** 659 * Ensures that the effect is repeating indefinitely or not. This is a lossy operation and 660 * should only be applied once to an original effect - it shouldn't be applied to the 661 * result of this method. 662 * 663 * <p>Non-repeating effects will be made repeating by looping the entire effect with the 664 * specified delay between each loop. The delay is added irrespective of whether the effect 665 * already has a delay at the beginning or end. 666 * 667 * <p>Repeating effects will be left with their native repeating portion if it should be 668 * repeating, and otherwise the loop index is removed, so that the entire effect plays once. 669 * 670 * @param wantRepeating Whether the effect is required to be repeating or not. 671 * @param loopDelayMs The milliseconds to pause between loops, if repeating is to be added to 672 * the effect. Ignored if {@code repeating==false} or the effect is already 673 * repeating itself. No delay is added if <= 0. 674 * @return this if the effect already satisfies the repeating requirement, or a copy of this 675 * adjusted to repeat or not repeat as appropriate. 676 * @hide 677 */ 678 @NonNull applyRepeatingIndefinitely( boolean wantRepeating, int loopDelayMs)679 public abstract VibrationEffect applyRepeatingIndefinitely( 680 boolean wantRepeating, int loopDelayMs); 681 682 /** 683 * Scale given vibration intensity by the given factor. 684 * 685 * <p> This scale is not necessarily linear and may apply a gamma correction to the scale 686 * factor before using it. 687 * 688 * @param intensity relative intensity of the effect, must be between 0 and 1 689 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will 690 * scale down the intensity, values larger than 1 will scale up 691 * @return the scaled intensity which will be values within [0, 1]. 692 * 693 * @hide 694 */ scale(float intensity, float scaleFactor)695 public static float scale(float intensity, float scaleFactor) { 696 if (Flags.hapticsScaleV2Enabled()) { 697 if (Float.compare(scaleFactor, 1) <= 0 || Float.compare(intensity, 0) == 0) { 698 // Scaling down or scaling zero intensity is straightforward. 699 return scaleFactor * intensity; 700 } 701 // Using S * x / (1 + (S - 1) * x^2) as the scale up function to converge to 1.0. 702 return (scaleFactor * intensity) / (1 + (scaleFactor - 1) * intensity * intensity); 703 } 704 705 // Applying gamma correction to the scale factor, which is the same as encoding the input 706 // value, scaling it, then decoding the scaled value. 707 float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA); 708 709 if (scaleFactor <= 1) { 710 // Scale down is simply a gamma corrected application of scaleFactor to the intensity. 711 // Scale up requires a different curve to ensure the intensity will not become > 1. 712 return intensity * scale; 713 } 714 715 // Apply the scale factor a few more times to make the ramp curve closer to the raw scale. 716 float extraScale = MathUtils.pow(scaleFactor, 4f - scaleFactor); 717 float x = intensity * scale * extraScale; 718 float maxX = scale * extraScale; // scaled x for intensity == 1 719 720 float expX = MathUtils.exp(x); 721 float expMaxX = MathUtils.exp(maxX); 722 723 // Using f = tanh as the scale up function so the max value will converge. 724 // a = 1/f(maxX), used to scale f so that a*f(maxX) = 1 (the value will converge to 1). 725 float a = (expMaxX + 1f) / (expMaxX - 1f); 726 float fx = (expX - 1f) / (expX + 1f); 727 728 return MathUtils.constrain(a * fx, 0f, 1f); 729 } 730 731 /** 732 * Performs a linear scaling on the given vibration intensity by the given factor. 733 * 734 * @param intensity relative intensity of the effect, must be between 0 and 1. 735 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will 736 * scale down the intensity, values larger than 1 will scale up. 737 * @return the scaled intensity which will be values within [0, 1]. 738 * 739 * @hide 740 */ scaleLinearly(float intensity, float scaleFactor)741 public static float scaleLinearly(float intensity, float scaleFactor) { 742 return MathUtils.constrain(intensity * scaleFactor, 0f, 1f); 743 } 744 745 /** 746 * Returns a compact version of the {@link #toString()} result for debugging purposes. 747 * 748 * @hide 749 */ toDebugString()750 public abstract String toDebugString(); 751 752 /** @hide */ effectIdToString(int effectId)753 public static String effectIdToString(int effectId) { 754 return switch (effectId) { 755 case EFFECT_CLICK -> "CLICK"; 756 case EFFECT_TICK -> "TICK"; 757 case EFFECT_HEAVY_CLICK -> "HEAVY_CLICK"; 758 case EFFECT_DOUBLE_CLICK -> "DOUBLE_CLICK"; 759 case EFFECT_POP -> "POP"; 760 case EFFECT_THUD -> "THUD"; 761 case EFFECT_TEXTURE_TICK -> "TEXTURE_TICK"; 762 default -> Integer.toString(effectId); 763 }; 764 } 765 766 /** @hide */ effectStrengthToString(int effectStrength)767 public static String effectStrengthToString(int effectStrength) { 768 return switch (effectStrength) { 769 case EFFECT_STRENGTH_LIGHT -> "LIGHT"; 770 case EFFECT_STRENGTH_MEDIUM -> "MEDIUM"; 771 case EFFECT_STRENGTH_STRONG -> "STRONG"; 772 default -> Integer.toString(effectStrength); 773 }; 774 } 775 776 /** 777 * Transforms a {@link VibrationEffect} using a generic parameter. 778 * 779 * <p>This can be used for scaling effects based on user settings or adapting them to the 780 * capabilities of a specific device vibrator. 781 * 782 * @param <ParamT> The type of parameter to be used on the effect by this transformation 783 * @hide 784 */ 785 public interface Transformation<ParamT> { 786 787 /** Transforms given effect by applying the given parameter. */ 788 @NonNull 789 VibrationEffect transform(@NonNull VibrationEffect effect, @NonNull ParamT param); 790 } 791 792 /** 793 * Implementation of {@link VibrationEffect} described by a composition of one or more 794 * {@link VibrationEffectSegment}, with an optional index to represent repeating effects. 795 * 796 * @hide 797 */ 798 @TestApi 799 public static final class Composed extends VibrationEffect { 800 private final ArrayList<VibrationEffectSegment> mSegments; 801 private final int mRepeatIndex; 802 803 /** @hide */ 804 Composed(@NonNull Parcel in) { 805 this(Objects.requireNonNull(in.readArrayList( 806 VibrationEffectSegment.class.getClassLoader(), 807 VibrationEffectSegment.class)), 808 in.readInt()); 809 } 810 811 /** @hide */ 812 Composed(@NonNull VibrationEffectSegment segment) { 813 this(Arrays.asList(segment), /* repeatIndex= */ -1); 814 } 815 816 /** @hide */ 817 public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) { 818 super(); 819 mSegments = new ArrayList<>(segments); 820 mRepeatIndex = repeatIndex; 821 } 822 823 @NonNull 824 public List<VibrationEffectSegment> getSegments() { 825 return mSegments; 826 } 827 828 public int getRepeatIndex() { 829 return mRepeatIndex; 830 } 831 832 /** @hide */ 833 @Override 834 @Nullable 835 public long[] computeCreateWaveformOffOnTimingsOrNull() { 836 if (getRepeatIndex() >= 0) { 837 // Repeating effects cannot be fully represented as a long[] legacy pattern. 838 return null; 839 } 840 841 List<VibrationEffectSegment> segments = getSegments(); 842 843 // The maximum possible size of the final pattern is 1 plus the number of segments in 844 // the original effect. This is because we will add an empty "off" segment at the 845 // start of the pattern if the first segment of the original effect is an "on" segment. 846 // (because the legacy patterns start with an "off" pattern). Other than this one case, 847 // we will add the durations of back-to-back segments of similar amplitudes (amplitudes 848 // that are all "on" or "off") and create a pattern entry for the total duration, which 849 // will not take more number pattern entries than the number of segments processed. 850 long[] patternBuffer = new long[segments.size() + 1]; 851 int patternIndex = 0; 852 853 for (int i = 0; i < segments.size(); i++) { 854 StepSegment stepSegment = 855 castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i)); 856 if (stepSegment == null) { 857 // This means that there is 1 or more segments of this effect that is/are not a 858 // possible component of a legacy vibration pattern. Thus, the VibrationEffect 859 // does not have any equivalent legacy vibration pattern. 860 return null; 861 } 862 863 boolean isSegmentOff = stepSegment.getAmplitude() == 0; 864 // Even pattern indices are "off", and odd pattern indices are "on" 865 boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0; 866 if (isSegmentOff != isCurrentPatternIndexOff) { 867 // Move the pattern index one step ahead, so that the current segment's 868 // "off"/"on" property matches that of the index's 869 ++patternIndex; 870 } 871 patternBuffer[patternIndex] += stepSegment.getDuration(); 872 } 873 874 return Arrays.copyOf(patternBuffer, patternIndex + 1); 875 } 876 877 /** @hide */ 878 @Override 879 public void validate() { 880 int segmentCount = mSegments.size(); 881 boolean hasNonZeroDuration = false; 882 for (int i = 0; i < segmentCount; i++) { 883 VibrationEffectSegment segment = mSegments.get(i); 884 segment.validate(); 885 // A segment with unknown duration = -1 still counts as a non-zero duration. 886 hasNonZeroDuration |= segment.getDuration() != 0; 887 } 888 if (!hasNonZeroDuration) { 889 throw new IllegalArgumentException("at least one timing must be non-zero" 890 + " (segments=" + mSegments + ")"); 891 } 892 if (mRepeatIndex != -1) { 893 Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1, 894 "repeat index must be within the bounds of the segments (segments.length=" 895 + segmentCount + ", index=" + mRepeatIndex + ")"); 896 } 897 } 898 899 /** @hide */ 900 @Override 901 @Nullable 902 public VibrationEffect cropToLengthOrNull(int length) { 903 // drop repeating effects 904 if (mRepeatIndex >= 0) { 905 return null; 906 } 907 908 int segmentCount = mSegments.size(); 909 if (segmentCount <= length) { 910 return this; 911 } 912 913 ArrayList truncated = new ArrayList(mSegments.subList(0, length)); 914 Composed updated = new Composed(truncated, mRepeatIndex); 915 try { 916 updated.validate(); 917 } catch (IllegalArgumentException e) { 918 return null; 919 } 920 return updated; 921 } 922 923 @Override 924 public long getDuration() { 925 return getDuration(VibrationEffectSegment::getDuration); 926 } 927 928 /** @hide */ 929 @Override 930 public long getDuration(@Nullable VibratorInfo vibratorInfo) { 931 return getDuration(segment -> segment.getDuration(vibratorInfo)); 932 } 933 934 private long getDuration(Function<VibrationEffectSegment, Long> durationFn) { 935 if (mRepeatIndex >= 0) { 936 return Long.MAX_VALUE; 937 } 938 int segmentCount = mSegments.size(); 939 long totalDuration = 0; 940 for (int i = 0; i < segmentCount; i++) { 941 long segmentDuration = durationFn.apply(mSegments.get(i)); 942 if (segmentDuration < 0) { 943 return segmentDuration; 944 } 945 totalDuration += segmentDuration; 946 } 947 return totalDuration; 948 } 949 950 /** @hide */ 951 @Override 952 public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) { 953 for (VibrationEffectSegment segment : mSegments) { 954 if (!segment.areVibrationFeaturesSupported(vibratorInfo)) { 955 return false; 956 } 957 } 958 return true; 959 } 960 961 /** @hide */ 962 @Override 963 public boolean isHapticFeedbackCandidate() { 964 long totalDuration = getDuration(); 965 if (totalDuration > MAX_HAPTIC_FEEDBACK_DURATION) { 966 // Vibration duration is known and is longer than the max duration used to classify 967 // haptic feedbacks (or repeating indefinitely with duration == Long.MAX_VALUE). 968 return false; 969 } 970 int segmentCount = mSegments.size(); 971 if (segmentCount > MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE) { 972 // Vibration has some predefined or primitive constants, it should be limited to the 973 // max composition size used to classify haptic feedbacks. 974 return false; 975 } 976 totalDuration = 0; 977 for (int i = 0; i < segmentCount; i++) { 978 if (!mSegments.get(i).isHapticFeedbackCandidate()) { 979 // There is at least one segment that is not a candidate for a haptic feedback. 980 return false; 981 } 982 long segmentDuration = mSegments.get(i).getDuration(); 983 if (segmentDuration > 0) { 984 totalDuration += segmentDuration; 985 } 986 } 987 // Vibration might still have some ramp or step segments, check the known duration. 988 return totalDuration <= MAX_HAPTIC_FEEDBACK_DURATION; 989 } 990 991 /** @hide */ 992 @NonNull 993 @Override 994 public Composed resolve(int defaultAmplitude) { 995 return applyToSegments(VibrationEffectSegment::resolve, defaultAmplitude); 996 } 997 998 /** @hide */ 999 @NonNull 1000 @Override 1001 public VibrationEffect applyEffectStrength(int effectStrength) { 1002 return applyToSegments(VibrationEffectSegment::applyEffectStrength, effectStrength); 1003 } 1004 1005 /** @hide */ 1006 @NonNull 1007 @Override 1008 public Composed scale(float scaleFactor) { 1009 return applyToSegments(VibrationEffectSegment::scale, scaleFactor); 1010 } 1011 1012 /** @hide */ 1013 @NonNull 1014 @Override 1015 public Composed applyAdaptiveScale(float scaleFactor) { 1016 return applyToSegments(VibrationEffectSegment::scaleLinearly, scaleFactor); 1017 } 1018 1019 /** @hide */ 1020 @NonNull 1021 @Override 1022 public Composed applyRepeatingIndefinitely(boolean wantRepeating, int loopDelayMs) { 1023 boolean isRepeating = mRepeatIndex >= 0; 1024 if (isRepeating == wantRepeating) { 1025 return this; 1026 } else if (!wantRepeating) { 1027 return new Composed(mSegments, -1); 1028 } else if (loopDelayMs <= 0) { 1029 // Loop with no delay: repeat at index zero. 1030 return new Composed(mSegments, 0); 1031 } else { 1032 // Append a delay and loop. It doesn't matter that there's a delay on the 1033 // end because the looping is always indefinite until cancelled. 1034 ArrayList<VibrationEffectSegment> loopingSegments = 1035 new ArrayList<>(mSegments.size() + 1); 1036 loopingSegments.addAll(mSegments); 1037 loopingSegments.add( 1038 new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, loopDelayMs)); 1039 return new Composed(loopingSegments, 0); 1040 } 1041 } 1042 1043 @Override 1044 public boolean equals(@Nullable Object o) { 1045 if (this == o) { 1046 return true; 1047 } 1048 if (!(o instanceof Composed other)) { 1049 return false; 1050 } 1051 return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex; 1052 } 1053 1054 @Override 1055 public int hashCode() { 1056 return Objects.hash(mSegments, mRepeatIndex); 1057 } 1058 1059 @Override 1060 public String toString() { 1061 return "Composed{segments=" + mSegments 1062 + ", repeat=" + mRepeatIndex 1063 + "}"; 1064 } 1065 1066 /** @hide */ 1067 @Override 1068 public String toDebugString() { 1069 if (mSegments.size() == 1 && mRepeatIndex < 0) { 1070 // Simplify effect string, use the single segment to represent it. 1071 return mSegments.get(0).toDebugString(); 1072 } 1073 StringJoiner sj = new StringJoiner(",", "[", "]"); 1074 for (int i = 0; i < mSegments.size(); i++) { 1075 sj.add(mSegments.get(i).toDebugString()); 1076 } 1077 if (mRepeatIndex >= 0) { 1078 return String.format(Locale.ROOT, "%s, repeat=%d", sj, mRepeatIndex); 1079 } 1080 return sj.toString(); 1081 } 1082 1083 @Override 1084 public int describeContents() { 1085 return 0; 1086 } 1087 1088 @Override 1089 public void writeToParcel(@NonNull Parcel out, int flags) { 1090 out.writeInt(PARCEL_TOKEN_COMPOSED); 1091 out.writeList(mSegments); 1092 out.writeInt(mRepeatIndex); 1093 } 1094 1095 @NonNull 1096 public static final Creator<Composed> CREATOR = 1097 new Creator<Composed>() { 1098 @Override 1099 public Composed createFromParcel(Parcel in) { 1100 in.readInt(); // Skip the parcel type token 1101 return new Composed(in); 1102 } 1103 1104 @Override 1105 public Composed[] newArray(int size) { 1106 return new Composed[size]; 1107 } 1108 }; 1109 1110 /** 1111 * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it, 1112 * only if it can possibly be a segment for an effect created via 1113 * {@link #createWaveform(long[], int)}. Otherwise, returns {@code null}. 1114 */ 1115 @Nullable 1116 private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull( 1117 VibrationEffectSegment segment) { 1118 if (!(segment instanceof StepSegment)) { 1119 return null; 1120 } 1121 1122 StepSegment stepSegment = (StepSegment) segment; 1123 if (stepSegment.getFrequencyHz() != 0) { 1124 return null; 1125 } 1126 1127 float amplitude = stepSegment.getAmplitude(); 1128 if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) { 1129 return null; 1130 } 1131 1132 return stepSegment; 1133 } 1134 1135 private <T> Composed applyToSegments( 1136 BiFunction<VibrationEffectSegment, T, VibrationEffectSegment> function, T param) { 1137 int segmentCount = mSegments.size(); 1138 ArrayList<VibrationEffectSegment> updatedSegments = new ArrayList<>(segmentCount); 1139 for (int i = 0; i < segmentCount; i++) { 1140 updatedSegments.add(function.apply(mSegments.get(i), param)); 1141 } 1142 if (mSegments.equals(updatedSegments)) { 1143 return this; 1144 } 1145 Composed updated = new Composed(updatedSegments, mRepeatIndex); 1146 updated.validate(); 1147 return updated; 1148 } 1149 } 1150 1151 /** 1152 * Implementation of {@link VibrationEffect} described by a generic {@link PersistableBundle} 1153 * defined by vendors. 1154 * 1155 * @hide 1156 */ 1157 @TestApi 1158 @FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS) 1159 public static final class VendorEffect extends VibrationEffect { 1160 /** @hide */ 1161 public static final int DEFAULT_STRENGTH = VibrationEffect.EFFECT_STRENGTH_MEDIUM; 1162 /** @hide */ 1163 public static final float DEFAULT_SCALE = 1.0f; 1164 1165 private final PersistableBundle mVendorData; 1166 private final int mEffectStrength; 1167 private final float mScale; 1168 private final float mAdaptiveScale; 1169 1170 /** @hide */ 1171 VendorEffect(@NonNull Parcel in) { 1172 this(Objects.requireNonNull( 1173 in.readPersistableBundle(VibrationEffect.class.getClassLoader())), 1174 in.readInt(), in.readFloat(), in.readFloat()); 1175 } 1176 1177 /** @hide */ 1178 public VendorEffect(@NonNull PersistableBundle vendorData, int effectStrength, 1179 float scale, float adaptiveScale) { 1180 mVendorData = vendorData; 1181 mEffectStrength = effectStrength; 1182 mScale = scale; 1183 mAdaptiveScale = adaptiveScale; 1184 } 1185 1186 @NonNull 1187 public PersistableBundle getVendorData() { 1188 return mVendorData; 1189 } 1190 1191 public int getEffectStrength() { 1192 return mEffectStrength; 1193 } 1194 1195 public float getScale() { 1196 return mScale; 1197 } 1198 1199 public float getAdaptiveScale() { 1200 return mAdaptiveScale; 1201 } 1202 1203 /** @hide */ 1204 @Override 1205 @Nullable 1206 public long[] computeCreateWaveformOffOnTimingsOrNull() { 1207 return null; 1208 } 1209 1210 /** @hide */ 1211 @Override 1212 public void validate() { 1213 Preconditions.checkArgument(!mVendorData.isEmpty(), 1214 "Vendor effect bundle must be non-empty"); 1215 } 1216 1217 /** @hide */ 1218 @Override 1219 @Nullable 1220 public VibrationEffect cropToLengthOrNull(int length) { 1221 return null; 1222 } 1223 1224 @Override 1225 public long getDuration() { 1226 return -1; // UNKNOWN 1227 } 1228 1229 /** @hide */ 1230 @Override 1231 public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) { 1232 return vibratorInfo.hasCapability(IVibrator.CAP_PERFORM_VENDOR_EFFECTS); 1233 } 1234 1235 /** @hide */ 1236 @Override 1237 public boolean isHapticFeedbackCandidate() { 1238 return false; 1239 } 1240 1241 /** @hide */ 1242 @NonNull 1243 @Override 1244 public VendorEffect resolve(int defaultAmplitude) { 1245 return this; 1246 } 1247 1248 /** @hide */ 1249 @NonNull 1250 @Override 1251 public VibrationEffect applyEffectStrength(int effectStrength) { 1252 if (mEffectStrength == effectStrength) { 1253 return this; 1254 } 1255 VendorEffect updated = new VendorEffect(mVendorData, effectStrength, mScale, 1256 mAdaptiveScale); 1257 updated.validate(); 1258 return updated; 1259 } 1260 1261 /** @hide */ 1262 @NonNull 1263 @Override 1264 public VendorEffect scale(float scaleFactor) { 1265 if (Float.compare(mScale, scaleFactor) == 0) { 1266 return this; 1267 } 1268 VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, scaleFactor, 1269 mAdaptiveScale); 1270 updated.validate(); 1271 return updated; 1272 } 1273 1274 /** @hide */ 1275 @NonNull 1276 @Override 1277 public VibrationEffect applyAdaptiveScale(float scaleFactor) { 1278 if (Float.compare(mAdaptiveScale, scaleFactor) == 0) { 1279 return this; 1280 } 1281 VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, mScale, 1282 scaleFactor); 1283 updated.validate(); 1284 return updated; 1285 } 1286 1287 /** @hide */ 1288 @NonNull 1289 @Override 1290 public VendorEffect applyRepeatingIndefinitely(boolean wantRepeating, int loopDelayMs) { 1291 return this; 1292 } 1293 1294 @Override 1295 public boolean equals(@Nullable Object o) { 1296 if (this == o) { 1297 return true; 1298 } 1299 if (!(o instanceof VendorEffect other)) { 1300 return false; 1301 } 1302 return mEffectStrength == other.mEffectStrength 1303 && (Float.compare(mScale, other.mScale) == 0) 1304 && (Float.compare(mAdaptiveScale, other.mAdaptiveScale) == 0) 1305 && isPersistableBundleEquals(mVendorData, other.mVendorData); 1306 } 1307 1308 @Override 1309 public int hashCode() { 1310 // PersistableBundle does not implement hashCode, so use its size as a shortcut. 1311 return Objects.hash(mVendorData.size(), mEffectStrength, mScale, mAdaptiveScale); 1312 } 1313 1314 @Override 1315 public String toString() { 1316 return String.format(Locale.ROOT, 1317 "VendorEffect{vendorData=%s, strength=%s, scale=%.2f, adaptiveScale=%.2f}", 1318 mVendorData, effectStrengthToString(mEffectStrength), mScale, mAdaptiveScale); 1319 } 1320 1321 /** @hide */ 1322 @Override 1323 public String toDebugString() { 1324 return String.format(Locale.ROOT, 1325 "vendorEffect=%s, strength=%s, scale=%.2f, adaptiveScale=%.2f", 1326 mVendorData.toShortString(), effectStrengthToString(mEffectStrength), 1327 mScale, mAdaptiveScale); 1328 } 1329 1330 @Override 1331 public int describeContents() { 1332 return 0; 1333 } 1334 1335 @Override 1336 public void writeToParcel(@NonNull Parcel out, int flags) { 1337 out.writeInt(PARCEL_TOKEN_VENDOR_EFFECT); 1338 out.writePersistableBundle(mVendorData); 1339 out.writeInt(mEffectStrength); 1340 out.writeFloat(mScale); 1341 out.writeFloat(mAdaptiveScale); 1342 } 1343 1344 /** 1345 * Compares two {@link PersistableBundle} objects are equals. 1346 */ 1347 private static boolean isPersistableBundleEquals( 1348 PersistableBundle first, PersistableBundle second) { 1349 if (first == second) { 1350 return true; 1351 } 1352 if (first == null || second == null || first.size() != second.size()) { 1353 return false; 1354 } 1355 for (String key : first.keySet()) { 1356 if (!isPersistableBundleSupportedValueEquals(first.get(key), second.get(key))) { 1357 return false; 1358 } 1359 } 1360 return true; 1361 } 1362 1363 /** 1364 * Compares two values which type is supported by {@link PersistableBundle}. 1365 * 1366 * <p>If the type isn't supported. The equality is done by {@link Object#equals(Object)}. 1367 */ 1368 private static boolean isPersistableBundleSupportedValueEquals( 1369 Object first, Object second) { 1370 if (first == second) { 1371 return true; 1372 } else if (first == null || second == null 1373 || !first.getClass().equals(second.getClass())) { 1374 return false; 1375 } else if (first instanceof PersistableBundle) { 1376 return isPersistableBundleEquals( 1377 (PersistableBundle) first, (PersistableBundle) second); 1378 } else if (first instanceof int[]) { 1379 return Arrays.equals((int[]) first, (int[]) second); 1380 } else if (first instanceof long[]) { 1381 return Arrays.equals((long[]) first, (long[]) second); 1382 } else if (first instanceof double[]) { 1383 return Arrays.equals((double[]) first, (double[]) second); 1384 } else if (first instanceof boolean[]) { 1385 return Arrays.equals((boolean[]) first, (boolean[]) second); 1386 } else if (first instanceof String[]) { 1387 return Arrays.equals((String[]) first, (String[]) second); 1388 } else { 1389 return Objects.equals(first, second); 1390 } 1391 } 1392 1393 @NonNull 1394 public static final Creator<VendorEffect> CREATOR = 1395 new Creator<VendorEffect>() { 1396 @Override 1397 public VendorEffect createFromParcel(Parcel in) { 1398 in.readInt(); // Skip the parcel type token 1399 return new VendorEffect(in); 1400 } 1401 1402 @Override 1403 public VendorEffect[] newArray(int size) { 1404 return new VendorEffect[size]; 1405 } 1406 }; 1407 } 1408 1409 /** 1410 * Creates a new {@link VibrationEffect} that repeats the given effect indefinitely. 1411 * 1412 * <p>The input vibration must not be a repeating vibration. If it is, an 1413 * {@link IllegalArgumentException} will be thrown. 1414 * 1415 * @param effect The {@link VibrationEffect} that will be repeated. 1416 * @return A {@link VibrationEffect} that repeats the effect indefinitely. 1417 * @throws IllegalArgumentException if the effect is already a repeating vibration. 1418 */ 1419 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 1420 @NonNull 1421 public static VibrationEffect createRepeatingEffect(@NonNull VibrationEffect effect) { 1422 return VibrationEffect.startComposition() 1423 .repeatEffectIndefinitely(effect) 1424 .compose(); 1425 } 1426 1427 /** 1428 * Creates a new {@link VibrationEffect} by merging the preamble and repeating vibration effect. 1429 * 1430 * <p>Neither input vibration may already be repeating. An {@link IllegalArgumentException} will 1431 * be thrown if either input vibration is set to repeat indefinitely. 1432 * 1433 * @param preamble The starting vibration effect, which must be finite. 1434 * @param repeatingEffect The vibration effect to be repeated indefinitely after the preamble. 1435 * @return A {@link VibrationEffect} that plays the preamble once followed by the 1436 * `repeatingEffect` indefinitely. 1437 * @throws IllegalArgumentException if either preamble or repeatingEffect is already a repeating 1438 * vibration. 1439 */ 1440 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 1441 @NonNull 1442 public static VibrationEffect createRepeatingEffect(@NonNull VibrationEffect preamble, 1443 @NonNull VibrationEffect repeatingEffect) { 1444 Preconditions.checkArgument(preamble.getDuration() < Long.MAX_VALUE, 1445 "Can't repeat an indefinitely repeating effect."); 1446 return VibrationEffect.startComposition() 1447 .addEffect(preamble) 1448 .repeatEffectIndefinitely(repeatingEffect) 1449 .compose(); 1450 } 1451 1452 /** 1453 * A composition of haptic elements that are combined to be playable as a single 1454 * {@link VibrationEffect}. 1455 * 1456 * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and 1457 * can be added to a composition to create a custom vibration effect. Here is an example of an 1458 * effect that grows in intensity and then dies off, with a longer rising portion for emphasis 1459 * and an extra tick 100ms after: 1460 * 1461 * <pre> 1462 * {@code VibrationEffect effect = VibrationEffect.startComposition() 1463 * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f) 1464 * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f) 1465 * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100) 1466 * .compose();}</pre> 1467 * 1468 * <p>When choosing to play a composed effect, you should check that individual components are 1469 * supported by the device by using {@link Vibrator#arePrimitivesSupported}. 1470 * 1471 * @see VibrationEffect#startComposition() 1472 */ 1473 public static final class Composition { 1474 /** @hide */ 1475 @IntDef(prefix = { "PRIMITIVE_" }, value = { 1476 PRIMITIVE_CLICK, 1477 PRIMITIVE_THUD, 1478 PRIMITIVE_SPIN, 1479 PRIMITIVE_QUICK_RISE, 1480 PRIMITIVE_SLOW_RISE, 1481 PRIMITIVE_QUICK_FALL, 1482 PRIMITIVE_TICK, 1483 PRIMITIVE_LOW_TICK, 1484 }) 1485 @Retention(RetentionPolicy.SOURCE) 1486 public @interface PrimitiveType { 1487 } 1488 1489 /** @hide */ 1490 @IntDef(prefix = { "DELAY_TYPE_" }, value = { 1491 DELAY_TYPE_PAUSE, 1492 DELAY_TYPE_RELATIVE_START_OFFSET, 1493 }) 1494 @Retention(RetentionPolicy.SOURCE) 1495 public @interface DelayType { 1496 } 1497 1498 /** 1499 * Exception thrown when adding an element to a {@link Composition} that already ends in an 1500 * indefinitely repeating effect. 1501 * @hide 1502 */ 1503 @TestApi 1504 public static final class UnreachableAfterRepeatingIndefinitelyException 1505 extends IllegalStateException { 1506 UnreachableAfterRepeatingIndefinitelyException() { 1507 super("Compositions ending in an indefinitely repeating effect can't be extended"); 1508 } 1509 } 1510 1511 /** 1512 * No haptic effect. Used to generate extended delays between primitives. 1513 * 1514 * @hide 1515 */ 1516 public static final int PRIMITIVE_NOOP = 0; 1517 /** 1518 * This effect should produce a sharp, crisp click sensation. 1519 */ 1520 public static final int PRIMITIVE_CLICK = 1; 1521 /** 1522 * A haptic effect that simulates downwards movement with gravity. Often 1523 * followed by extra energy of hitting and reverberation to augment 1524 * physicality. 1525 */ 1526 public static final int PRIMITIVE_THUD = 2; 1527 /** 1528 * A haptic effect that simulates spinning momentum. 1529 */ 1530 public static final int PRIMITIVE_SPIN = 3; 1531 /** 1532 * A haptic effect that simulates quick upward movement against gravity. 1533 */ 1534 public static final int PRIMITIVE_QUICK_RISE = 4; 1535 /** 1536 * A haptic effect that simulates slow upward movement against gravity. 1537 */ 1538 public static final int PRIMITIVE_SLOW_RISE = 5; 1539 /** 1540 * A haptic effect that simulates quick downwards movement with gravity. 1541 */ 1542 public static final int PRIMITIVE_QUICK_FALL = 6; 1543 /** 1544 * This very short effect should produce a light crisp sensation intended 1545 * to be used repetitively for dynamic feedback. 1546 */ 1547 // Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK 1548 public static final int PRIMITIVE_TICK = 7; 1549 /** 1550 * This very short low frequency effect should produce a light crisp sensation 1551 * intended to be used repetitively for dynamic feedback. 1552 */ 1553 // Internally this maps to the HAL constant CompositePrimitive::LOW_TICK 1554 public static final int PRIMITIVE_LOW_TICK = 8; 1555 1556 /** 1557 * The delay represents a pause in the composition between the end of the previous primitive 1558 * and the beginning of the next one. 1559 * 1560 * <p>The primitive will start after the requested pause after the last primitive ended. 1561 * The actual time the primitive will be played depends on the previous primitive's actual 1562 * duration on the device hardware. This enables the combination of primitives to create 1563 * more complex effects based on how close to each other they'll play. Here is an example: 1564 * 1565 * <pre> 1566 * VibrationEffect popEffect = VibrationEffect.startComposition() 1567 * .addPrimitive(PRIMITIVE_QUICK_RISE) 1568 * .addPrimitive(PRIMITIVE_CLICK, 0.7, 50, DELAY_TYPE_PAUSE) 1569 * .compose() 1570 * </pre> 1571 */ 1572 @FlaggedApi(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) 1573 public static final int DELAY_TYPE_PAUSE = 0; 1574 1575 /** 1576 * The delay represents an offset before starting this primitive, relative to the start 1577 * time of the previous primitive in the composition. 1578 * 1579 * <p>The primitive will start at the requested fixed time after the last primitive started, 1580 * independently of that primitive's actual duration on the device hardware. This enables 1581 * precise timings of primitives within a composition, ensuring they'll be played at the 1582 * desired intervals. Here is an example: 1583 * 1584 * <pre> 1585 * VibrationEffect.startComposition() 1586 * .addPrimitive(PRIMITIVE_CLICK, 1.0) 1587 * .addPrimitive(PRIMITIVE_TICK, 1.0, 20, DELAY_TYPE_RELATIVE_START_OFFSET) 1588 * .addPrimitive(PRIMITIVE_THUD, 1.0, 80, DELAY_TYPE_RELATIVE_START_OFFSET) 1589 * .compose() 1590 * </pre> 1591 * 1592 * Will be performed on the device as follows: 1593 * 1594 * <pre> 1595 * 0ms 20ms 100ms 1596 * PRIMITIVE_CLICK---PRIMITIVE_TICK-----------PRIMITIVE_THUD 1597 * </pre> 1598 * 1599 * <p>A primitive will be dropped from the composition if it overlaps with previous ones. 1600 */ 1601 @FlaggedApi(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) 1602 public static final int DELAY_TYPE_RELATIVE_START_OFFSET = 1; 1603 1604 private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); 1605 private int mRepeatIndex = -1; 1606 1607 Composition() {} 1608 1609 /** 1610 * Adds a time duration to the current composition, during which the vibrator will be 1611 * turned off. 1612 * 1613 * @param duration The length of time the vibrator should be off. Value must be non-negative 1614 * and will be truncated to milliseconds. 1615 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1616 * 1617 * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently 1618 * ending with a repeating effect. 1619 * @hide 1620 */ 1621 @TestApi 1622 @NonNull 1623 public Composition addOffDuration(@NonNull Duration duration) { 1624 int durationMs = (int) duration.toMillis(); 1625 Preconditions.checkArgumentNonnegative(durationMs, "Off period must be non-negative"); 1626 if (durationMs > 0) { 1627 // Created a segment sustaining the zero amplitude to represent the delay. 1628 addSegment(new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, 1629 (int) duration.toMillis())); 1630 } 1631 return this; 1632 } 1633 1634 /** 1635 * Add a haptic effect to the end of the current composition. 1636 * 1637 * <p>If this effect is repeating (e.g. created by {@link VibrationEffect#createWaveform} 1638 * with a non-negative repeat index, or created by another composition that has effects 1639 * repeating indefinitely), then no more effects or primitives will be accepted by this 1640 * composition after this method. Such effects should be cancelled via 1641 * {@link Vibrator#cancel()}. 1642 * 1643 * @param effect The effect to add to the end of this composition. 1644 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1645 * 1646 * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently 1647 * ending with a repeating effect. 1648 * @hide 1649 */ 1650 @TestApi 1651 @NonNull 1652 public Composition addEffect(@NonNull VibrationEffect effect) { 1653 return addSegments(effect); 1654 } 1655 1656 /** 1657 * Add a haptic effect to the end of the current composition and play it on repeat, 1658 * indefinitely. 1659 * 1660 * <p>The entire effect will be played on repeat, indefinitely, after all other elements 1661 * already added to this composition are played. No more effects or primitives will be 1662 * accepted by this composition after this method. Such effects should be cancelled via 1663 * {@link Vibrator#cancel()}. 1664 * 1665 * @param effect The effect to add to the end of this composition, must be finite. 1666 * @return This {@link Composition} object to enable adding multiple elements in one chain, 1667 * although only {@link #compose()} can follow this call. 1668 * 1669 * @throws IllegalArgumentException if the given effect is already repeating indefinitely. 1670 * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently 1671 * ending with a repeating effect. 1672 * @hide 1673 */ 1674 @TestApi 1675 @NonNull 1676 public Composition repeatEffectIndefinitely(@NonNull VibrationEffect effect) { 1677 Preconditions.checkArgument(effect.getDuration() < Long.MAX_VALUE, 1678 "Can't repeat an indefinitely repeating effect. Consider addEffect instead."); 1679 int previousSegmentCount = mSegments.size(); 1680 addSegments(effect); 1681 // Set repeat after segments were added, since addSegments checks this index. 1682 mRepeatIndex = previousSegmentCount; 1683 return this; 1684 } 1685 1686 /** 1687 * Add a haptic primitive to the end of the current composition. 1688 * 1689 * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a 1690 * default scale applied. 1691 * 1692 * @param primitiveId The primitive to add 1693 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1694 */ 1695 @NonNull 1696 public Composition addPrimitive(@PrimitiveType int primitiveId) { 1697 return addPrimitive(primitiveId, PrimitiveSegment.DEFAULT_SCALE); 1698 } 1699 1700 /** 1701 * Add a haptic primitive to the end of the current composition. 1702 * 1703 * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay. 1704 * 1705 * @param primitiveId The primitive to add 1706 * @param scale The scale to apply to the intensity of the primitive. 1707 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1708 */ 1709 @NonNull 1710 public Composition addPrimitive(@PrimitiveType int primitiveId, 1711 @FloatRange(from = 0f, to = 1f) float scale) { 1712 return addPrimitive(primitiveId, scale, PrimitiveSegment.DEFAULT_DELAY_MILLIS); 1713 } 1714 1715 /** 1716 * Add a haptic primitive to the end of the current composition. 1717 * 1718 * <p>Similar to {@link #addPrimitive(int, float, int, int)}, but default 1719 * delay type applied is {@link #DELAY_TYPE_PAUSE}. 1720 * 1721 * @param primitiveId The primitive to add 1722 * @param scale The scale to apply to the intensity of the primitive. 1723 * @param delay The amount of time in milliseconds to wait between the end of the last 1724 * primitive and the beginning of this one (i.e. a pause in the composition). 1725 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1726 */ 1727 @NonNull 1728 public Composition addPrimitive(@PrimitiveType int primitiveId, 1729 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) { 1730 return addPrimitive(primitiveId, scale, delay, PrimitiveSegment.DEFAULT_DELAY_TYPE); 1731 } 1732 1733 /** 1734 * Add a haptic primitive to the end of the current composition. 1735 * 1736 * @param primitiveId The primitive to add 1737 * @param scale The scale to apply to the intensity of the primitive. 1738 * @param delay The amount of time in milliseconds to wait before playing this primitive, 1739 * as defined by the given {@code delayType}. 1740 * @param delayType The type of delay to be applied, e.g. a pause between last primitive and 1741 * this one or a start offset. 1742 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1743 */ 1744 @FlaggedApi(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) 1745 @NonNull 1746 public Composition addPrimitive(@PrimitiveType int primitiveId, 1747 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay, 1748 @DelayType int delayType) { 1749 PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, delay, delayType); 1750 primitive.validate(); 1751 return addSegment(primitive); 1752 } 1753 1754 private Composition addSegment(VibrationEffectSegment segment) { 1755 if (mRepeatIndex >= 0) { 1756 throw new UnreachableAfterRepeatingIndefinitelyException(); 1757 } 1758 mSegments.add(segment); 1759 return this; 1760 } 1761 1762 private Composition addSegments(VibrationEffect effect) { 1763 if (mRepeatIndex >= 0) { 1764 throw new UnreachableAfterRepeatingIndefinitelyException(); 1765 } 1766 if (!(effect instanceof Composed composed)) { 1767 throw new IllegalArgumentException("Can't add vendor effects to composition."); 1768 } 1769 if (composed.getRepeatIndex() >= 0) { 1770 // Start repeating from the index relative to the composed waveform. 1771 mRepeatIndex = mSegments.size() + composed.getRepeatIndex(); 1772 } 1773 mSegments.addAll(composed.getSegments()); 1774 return this; 1775 } 1776 1777 /** 1778 * Compose all of the added primitives together into a single {@link VibrationEffect}. 1779 * 1780 * <p>The {@link Composition} object is still valid after this call, so you can continue 1781 * adding more primitives to it and generating more {@link VibrationEffect}s by calling this 1782 * method again. 1783 * 1784 * @return The {@link VibrationEffect} resulting from the composition of the primitives. 1785 */ 1786 @NonNull 1787 public VibrationEffect compose() { 1788 if (mSegments.isEmpty()) { 1789 throw new IllegalStateException( 1790 "Composition must have at least one element to compose."); 1791 } 1792 VibrationEffect effect = new Composed(mSegments, mRepeatIndex); 1793 effect.validate(); 1794 return effect; 1795 } 1796 1797 /** 1798 * Convert the primitive ID to a human readable string for debugging. 1799 * @param id The ID to convert 1800 * @return The ID in a human readable format. 1801 * @hide 1802 */ 1803 public static String primitiveToString(@PrimitiveType int id) { 1804 return switch (id) { 1805 case PRIMITIVE_NOOP -> "NOOP"; 1806 case PRIMITIVE_CLICK -> "CLICK"; 1807 case PRIMITIVE_THUD -> "THUD"; 1808 case PRIMITIVE_SPIN -> "SPIN"; 1809 case PRIMITIVE_QUICK_RISE -> "QUICK_RISE"; 1810 case PRIMITIVE_SLOW_RISE -> "SLOW_RISE"; 1811 case PRIMITIVE_QUICK_FALL -> "QUICK_FALL"; 1812 case PRIMITIVE_TICK -> "TICK"; 1813 case PRIMITIVE_LOW_TICK -> "LOW_TICK"; 1814 default -> Integer.toString(id); 1815 }; 1816 } 1817 1818 /** 1819 * Convert the delay type to a human readable string for debugging. 1820 * @param type The delay type to convert 1821 * @return The delay type in a human readable format. 1822 * @hide 1823 */ 1824 public static String delayTypeToString(@DelayType int type) { 1825 return switch (type) { 1826 case DELAY_TYPE_PAUSE -> "PAUSE"; 1827 case DELAY_TYPE_RELATIVE_START_OFFSET -> "START_OFFSET"; 1828 default -> Integer.toString(type); 1829 }; 1830 } 1831 } 1832 1833 /** 1834 * A builder for waveform effects described by its envelope. 1835 * 1836 * <p>Waveform effect envelopes are defined by one or more control points describing a target 1837 * vibration amplitude and frequency, and a duration to reach those targets. The vibrator 1838 * will perform smooth transitions between control points. 1839 * 1840 * <p>For example, the following code ramps a vibrator from off to full amplitude at 120Hz over 1841 * 100ms, holds that state for 200ms, and then ramps back down over 100ms: 1842 * 1843 * <pre>{@code 1844 * VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder() 1845 * .addControlPoint(1.0f, 120f, 100) 1846 * .addControlPoint(1.0f, 120f, 200) 1847 * .addControlPoint(0.0f, 120f, 100) 1848 * .build(); 1849 * }</pre> 1850 * 1851 * <p>The builder automatically starts all effects at 0 amplitude. 1852 * 1853 * <p>It is crucial to ensure that the frequency range used in your effect is compatible with 1854 * the device's capabilities. The framework will not play any frequencies that fall partially 1855 * or completely outside the device's supported range. It will also not attempt to correct or 1856 * modify these frequencies. 1857 * 1858 * <p>Therefore, it is strongly recommended that you design your haptic effects with the 1859 * device's frequency profile in mind. You can obtain the supported frequency range and other 1860 * relevant frequency-related information by getting the 1861 * {@link android.os.vibrator.VibratorFrequencyProfile} using the 1862 * {@link Vibrator#getFrequencyProfile()} method. 1863 * 1864 * <p>In addition to these limitations, when designing vibration patterns, it is important to 1865 * consider the physical limitations of the vibration actuator. These limitations include 1866 * factors such as the maximum number of control points allowed in an envelope effect, the 1867 * minimum and maximum durations permitted for each control point, and the maximum overall 1868 * duration of the effect. If a pattern exceeds the maximum number of allowed control points, 1869 * the framework will automatically break down the effect to ensure it plays correctly. 1870 * 1871 * <p>You can use the following APIs to obtain these limits: 1872 * <ul> 1873 * <li>Maximum envelope control points: {@link VibratorEnvelopeEffectInfo#getMaxSize()} 1874 * <li>Minimum control point duration: 1875 * {@link VibratorEnvelopeEffectInfo#getMinControlPointDurationMillis()} 1876 * <li>Maximum control point duration: 1877 * {@link VibratorEnvelopeEffectInfo#getMaxControlPointDurationMillis()} 1878 * <li>Maximum total effect duration: {@link VibratorEnvelopeEffectInfo#getMaxDurationMillis()} 1879 * </ul> 1880 */ 1881 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 1882 public static final class WaveformEnvelopeBuilder { 1883 1884 private ArrayList<PwleSegment> mSegments = new ArrayList<>(); 1885 private float mLastAmplitude = 0f; 1886 private float mLastFrequencyHz = Float.NaN; 1887 1888 public WaveformEnvelopeBuilder() {} 1889 1890 /** 1891 * Sets the initial frequency for the waveform in Hertz. 1892 * 1893 * <p>The effect will start vibrating at this frequency when it transitions to the 1894 * amplitude and frequency defined by the first control point. 1895 * 1896 * <p>The frequency must be greater than zero and within the supported range. To determine 1897 * the supported range, use {@link Vibrator#getFrequencyProfile()}. Creating 1898 * effects using frequencies outside this range will result in the vibration not playing. 1899 * 1900 * @param initialFrequencyHz The starting frequency of the vibration, in Hz. Must be 1901 * greater than zero. 1902 */ 1903 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 1904 @SuppressWarnings("MissingGetterMatchingBuilder")// No getter to initial frequency once set. 1905 @NonNull 1906 public WaveformEnvelopeBuilder setInitialFrequencyHz( 1907 @FloatRange(from = 0) float initialFrequencyHz) { 1908 1909 if (mSegments.isEmpty()) { 1910 mLastFrequencyHz = initialFrequencyHz; 1911 } else { 1912 PwleSegment firstSegment = mSegments.getFirst(); 1913 mSegments.set(0, new PwleSegment( 1914 firstSegment.getStartAmplitude(), 1915 firstSegment.getEndAmplitude(), 1916 initialFrequencyHz, // Update start frequency 1917 firstSegment.getEndFrequencyHz(), 1918 firstSegment.getDuration())); 1919 } 1920 1921 return this; 1922 } 1923 1924 /** 1925 * Adds a new control point to the end of this waveform envelope. 1926 * 1927 * <p>Amplitude defines the vibrator's strength at this frequency, ranging from 0 (off) to 1 1928 * (maximum achievable strength). This value scales linearly with output strength, not 1929 * perceived intensity. It's determined by the actuator response curve. 1930 * 1931 * <p>Frequency must be greater than zero and within the supported range. To determine 1932 * the supported range, use {@link Vibrator#getFrequencyProfile()}. Creating 1933 * effects using frequencies outside this range will result in the vibration not playing. 1934 * 1935 * <p>Time specifies the duration (in milliseconds) for the vibrator to smoothly transition 1936 * from the previous control point to this new one. It must be greater than zero. To 1937 * transition as quickly as possible, use 1938 * {@link VibratorEnvelopeEffectInfo#getMinControlPointDurationMillis()}. 1939 * 1940 * @param amplitude The amplitude value between 0 and 1, inclusive. 0 represents the 1941 * vibrator being off, and 1 represents the maximum achievable 1942 * amplitude 1943 * at this frequency. 1944 * @param frequencyHz The frequency in Hz, must be greater than zero. 1945 * @param durationMillis The transition time in milliseconds. 1946 */ 1947 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 1948 @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created. 1949 @NonNull 1950 public WaveformEnvelopeBuilder addControlPoint( 1951 @FloatRange(from = 0, to = 1) float amplitude, 1952 @FloatRange(from = 0) float frequencyHz, @DurationMillisLong long durationMillis) { 1953 1954 if (Float.isNaN(mLastFrequencyHz)) { 1955 mLastFrequencyHz = frequencyHz; 1956 } 1957 1958 mSegments.add(new PwleSegment(mLastAmplitude, amplitude, mLastFrequencyHz, frequencyHz, 1959 durationMillis)); 1960 1961 mLastAmplitude = amplitude; 1962 mLastFrequencyHz = frequencyHz; 1963 1964 return this; 1965 } 1966 1967 /** 1968 * Build the waveform as a single {@link VibrationEffect}. 1969 * 1970 * <p>The {@link WaveformEnvelopeBuilder} object is still valid after this call, so you can 1971 * continue adding more primitives to it and generating more {@link VibrationEffect}s by 1972 * calling this method again. 1973 * 1974 * @return The {@link VibrationEffect} resulting from the list of control points. 1975 * @throws IllegalStateException if no control points were added to the builder. 1976 */ 1977 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 1978 @NonNull 1979 public VibrationEffect build() { 1980 if (mSegments.isEmpty()) { 1981 throw new IllegalStateException( 1982 "WaveformEnvelopeBuilder must have at least one control point to build."); 1983 } 1984 VibrationEffect effect = new Composed(mSegments, /* repeatIndex= */ -1); 1985 effect.validate(); 1986 return effect; 1987 } 1988 } 1989 1990 /** 1991 * A builder for waveform effects defined by their envelope, designed to provide a consistent 1992 * haptic perception across devices with varying capabilities. 1993 * 1994 * <p>This builder simplifies the creation of waveform effects by automatically adapting them 1995 * to different devices based on their capabilities. Effects are defined by control points 1996 * specifying target vibration intensity and sharpness, along with durations to reach those 1997 * targets. The vibrator will smoothly transition between these control points. 1998 * 1999 * <p><b>Intensity:</b> Defines the overall strength of the vibration, ranging from 2000 * 0 (off) to 1 (maximum achievable strength). Higher values result in stronger 2001 * vibrations. Supported intensity values guarantee sensitivity levels (SL) above 2002 * 10 dB SL to ensure human perception. 2003 * 2004 * <p><b>Sharpness:</b> Defines the crispness of the vibration, ranging from 0 to 1. 2005 * Lower values produce smoother vibrations, while higher values create a sharper, 2006 * more snappy sensation. Sharpness is mapped to its equivalent frequency within 2007 * the device's supported frequency range. 2008 * 2009 * <p>While this builder handles most of the adaptation logic, it does come with some 2010 * limitations: 2011 * <ul> 2012 * <li>It may not use the full range of frequencies</li> 2013 * <li>It's restricted to a frequency range that can generate output of at least 10 db 2014 * SL</li> 2015 * <li>Effects must end with a zero intensity control point. Failure to end at a zero 2016 * intensity control point will result in an {@link IllegalStateException}.</li> 2017 * </ul> 2018 * 2019 * <p>The builder automatically starts all effects at 0 intensity. 2020 * 2021 * <p>To avoid these limitations and to have more control over the effects output, use 2022 * {@link WaveformEnvelopeBuilder}, where direct amplitude and frequency values can be used. 2023 * 2024 * <p>For optimal cross-device consistency, it's recommended to limit the number of control 2025 * points to a maximum of 16. However this is not mandatory, and if a pattern exceeds the 2026 * maximum number of allowed control points, the framework will automatically break down the 2027 * effect to ensure it plays correctly. 2028 * 2029 * <p>For example, the following code creates a vibration effect that ramps up the intensity 2030 * from a low-pitched to a high-pitched strong vibration over 500ms and then ramps it down to 2031 * 0 (off) over 100ms: 2032 * 2033 * <pre>{@code 2034 * VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder() 2035 * .setInitialSharpness(0.0f) 2036 * .addControlPoint(1.0f, 1.0f, 500) 2037 * .addControlPoint(0.0f, 1.0f, 100) 2038 * .build(); 2039 * }</pre> 2040 */ 2041 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 2042 public static final class BasicEnvelopeBuilder { 2043 2044 private ArrayList<BasicPwleSegment> mSegments = new ArrayList<>(); 2045 private float mLastIntensity = 0f; 2046 private float mLastSharpness = Float.NaN; 2047 2048 public BasicEnvelopeBuilder() {} 2049 2050 /** 2051 * Sets the initial sharpness for the basic envelope effect. 2052 * 2053 * <p>The effect will start vibrating at this sharpness when it transitions to the 2054 * intensity and sharpness defined by the first control point. 2055 * 2056 * <p> The sharpness defines the crispness of the vibration, ranging from 0 to 1. Lower 2057 * values translate to smoother vibrations, while higher values create a sharper more snappy 2058 * sensation. This value is mapped to the supported frequency range of the device. 2059 * 2060 * @param initialSharpness The starting sharpness of the vibration in the range of [0, 1]. 2061 */ 2062 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 2063 @SuppressWarnings("MissingGetterMatchingBuilder")// No getter to initial sharpness once set. 2064 @NonNull 2065 public BasicEnvelopeBuilder setInitialSharpness( 2066 @FloatRange(from = 0, to = 1) float initialSharpness) { 2067 2068 if (mSegments.isEmpty()) { 2069 mLastSharpness = initialSharpness; 2070 } else { 2071 BasicPwleSegment firstSegment = mSegments.getFirst(); 2072 mSegments.set(0, new BasicPwleSegment( 2073 firstSegment.getStartIntensity(), 2074 firstSegment.getEndIntensity(), 2075 initialSharpness, // Update start sharpness 2076 firstSegment.getEndSharpness(), 2077 firstSegment.getDuration())); 2078 } 2079 2080 return this; 2081 } 2082 2083 /** 2084 * Adds a new control point to the end of this waveform envelope. 2085 * 2086 * <p>Intensity defines the overall strength of the vibration, ranging from 0 (off) to 1 2087 * (maximum achievable strength). Higher values translate to stronger vibrations. 2088 * 2089 * <p>Sharpness defines the crispness of the vibration, ranging from 0 to 1. Lower 2090 * values translate to smoother vibrations, while higher values create a sharper more snappy 2091 * sensation. This value is mapped to the supported frequency range of the device. 2092 * 2093 * <p>Time specifies the duration (in milliseconds) for the vibrator to smoothly transition 2094 * from the previous control point to this new one. It must be greater than zero. To 2095 * transition as quickly as possible, use 2096 * {@link VibratorEnvelopeEffectInfo#getMinControlPointDurationMillis()}. 2097 * 2098 * @param intensity The target vibration intensity, ranging from 0 (off) to 1 (maximum 2099 * strength). 2100 * @param sharpness The target sharpness, ranging from 0 (smoothest) to 1 (sharpest). 2101 * @param durationMillis The transition time in milliseconds. 2102 */ 2103 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 2104 @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created. 2105 @NonNull 2106 public BasicEnvelopeBuilder addControlPoint( 2107 @FloatRange(from = 0, to = 1) float intensity, 2108 @FloatRange(from = 0, to = 1) float sharpness, 2109 @DurationMillisLong long durationMillis) { 2110 2111 if (Float.isNaN(mLastSharpness)) { 2112 mLastSharpness = sharpness; 2113 } 2114 2115 mSegments.add(new BasicPwleSegment(mLastIntensity, intensity, mLastSharpness, sharpness, 2116 durationMillis)); 2117 2118 mLastIntensity = intensity; 2119 mLastSharpness = sharpness; 2120 2121 return this; 2122 } 2123 2124 /** 2125 * Build the waveform as a single {@link VibrationEffect}. 2126 * 2127 * <p>The {@link BasicEnvelopeBuilder} object is still valid after this call, so you can 2128 * continue adding more primitives to it and generating more {@link VibrationEffect}s by 2129 * calling this method again. 2130 * 2131 * @return The {@link VibrationEffect} resulting from the list of control points. 2132 * @throws IllegalStateException if the last control point does not end at zero intensity. 2133 */ 2134 @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) 2135 @NonNull 2136 public VibrationEffect build() { 2137 if (mSegments.isEmpty()) { 2138 throw new IllegalStateException( 2139 "BasicEnvelopeBuilder must have at least one control point to build."); 2140 } 2141 if (mSegments.getLast().getEndIntensity() != 0) { 2142 throw new IllegalStateException( 2143 "Basic envelope effects must end at a zero intensity control point."); 2144 } 2145 VibrationEffect effect = new Composed(mSegments, /* repeatIndex= */ -1); 2146 effect.validate(); 2147 return effect; 2148 } 2149 2150 } 2151 2152 /** 2153 * A builder for waveform haptic effects. 2154 * 2155 * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration 2156 * parameters. These parameters can be the vibration amplitude, frequency, or both. 2157 * 2158 * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms 2159 * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms: 2160 * 2161 * <pre> 2162 * {@code import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; 2163 * import static android.os.VibrationEffect.VibrationParameter.targetFrequency; 2164 * 2165 * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60)) 2166 * .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) 2167 * .addSustain(Duration.ofMillis(200)) 2168 * .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) 2169 * .build();}</pre> 2170 * 2171 * <p>The initial state of the waveform can be set via 2172 * {@link VibrationEffect#startWaveform(VibrationParameter)} or 2173 * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial 2174 * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off, 2175 * represented by zero amplitude, at the vibrator's resonant frequency. 2176 * 2177 * <p>Repeating waveforms can be created by building the repeating block separately and adding 2178 * it to the end of a composition with 2179 * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}: 2180 * 2181 * <p>Note that physical vibration actuators have different reaction times for changing 2182 * amplitude and frequency. Durations specified here represent a timeline for the target 2183 * parameters, and quality of effects may be improved if the durations allow time for a 2184 * transition to be smoothly applied. 2185 * 2186 * <p>The following example illustrates both an initial state and a repeating section, using 2187 * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a 2188 * repeated beating effect with a rise that stretches out and a sharp finish. 2189 * 2190 * <pre> 2191 * {@code VibrationEffect patternToRepeat = VibrationEffect.startWaveform(targetAmplitude(0.2f)) 2192 * .addSustain(Duration.ofMillis(10)) 2193 * .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f)) 2194 * .addSustain(Duration.ofMillis(30)) 2195 * .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f)) 2196 * .addSustain(Duration.ofMillis(50)) 2197 * .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f)) 2198 * .build(); 2199 * 2200 * VibrationEffect effect = VibrationEffect.startComposition() 2201 * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) 2202 * .addOffDuration(Duration.ofMillis(20)) 2203 * .repeatEffectIndefinitely(patternToRepeat) 2204 * .compose();}</pre> 2205 * 2206 * <p>The amplitude step waveforms that can be created via 2207 * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with 2208 * {@link WaveformBuilder} by adding zero duration transitions: 2209 * 2210 * <pre> 2211 * {@code // These two effects are the same 2212 * VibrationEffect waveform = VibrationEffect.createWaveform( 2213 * new long[] { 10, 20, 30 }, // timings in milliseconds 2214 * new int[] { 51, 102, 204 }, // amplitudes in [0,255] 2215 * -1); // repeat index 2216 * 2217 * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f)) 2218 * .addSustain(Duration.ofMillis(10)) 2219 * .addTransition(Duration.ZERO, targetAmplitude(0.4f)) 2220 * .addSustain(Duration.ofMillis(20)) 2221 * .addTransition(Duration.ZERO, targetAmplitude(0.8f)) 2222 * .addSustain(Duration.ofMillis(30)) 2223 * .build();}</pre> 2224 * 2225 * @see VibrationEffect#startWaveform 2226 * @hide 2227 */ 2228 @TestApi 2229 public static final class WaveformBuilder { 2230 // Epsilon used for float comparison of amplitude and frequency values on transitions. 2231 private static final float EPSILON = 1e-5f; 2232 2233 private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); 2234 private float mLastAmplitude = 0f; 2235 private float mLastFrequencyHz = 0f; 2236 2237 WaveformBuilder() {} 2238 2239 /** 2240 * Add a transition to new vibration parameter value to the end of this waveform. 2241 * 2242 * <p>The duration represents how long the vibrator should take to smoothly transition to 2243 * the new vibration parameter. If the duration is zero then the vibrator will jump to the 2244 * new value as fast as possible. 2245 * 2246 * <p>Vibration parameter values will be truncated to conform to the device capabilities 2247 * according to the {@link VibratorFrequencyProfileLegacy}. 2248 * 2249 * @param duration The length of time this transition should take. Value must be 2250 * non-negative and will be truncated to milliseconds. 2251 * @param targetParameter The new target {@link VibrationParameter} value to be reached 2252 * after the given duration. 2253 * @return This {@link WaveformBuilder} object to enable adding multiple transitions in 2254 * chain. 2255 * @hide 2256 */ 2257 @TestApi 2258 @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created. 2259 @NonNull 2260 public WaveformBuilder addTransition(@NonNull Duration duration, 2261 @NonNull VibrationParameter targetParameter) { 2262 Preconditions.checkNotNull(duration, "Duration is null"); 2263 checkVibrationParameter(targetParameter, "targetParameter"); 2264 float amplitude = extractTargetAmplitude(targetParameter, /* target2= */ null); 2265 float frequencyHz = extractTargetFrequency(targetParameter, /* target2= */ null); 2266 addTransitionSegment(duration, amplitude, frequencyHz); 2267 return this; 2268 } 2269 2270 /** 2271 * Add a transition to new vibration parameters to the end of this waveform. 2272 * 2273 * <p>The duration represents how long the vibrator should take to smoothly transition to 2274 * the new vibration parameters. If the duration is zero then the vibrator will jump to the 2275 * new values as fast as possible. 2276 * 2277 * <p>Vibration parameters values will be truncated to conform to the device capabilities 2278 * according to the {@link VibratorFrequencyProfileLegacy}. 2279 * 2280 * @param duration The length of time this transition should take. Value must be 2281 * non-negative and will be truncated to milliseconds. 2282 * @param targetParameter1 The first target {@link VibrationParameter} value to be reached 2283 * after the given duration. 2284 * @param targetParameter2 The second target {@link VibrationParameter} value to be reached 2285 * after the given duration, must be a different type of parameter 2286 * than the one specified by the first argument. 2287 * @return This {@link WaveformBuilder} object to enable adding multiple transitions in 2288 * chain. 2289 * @hide 2290 */ 2291 @TestApi 2292 @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created. 2293 @NonNull 2294 public WaveformBuilder addTransition(@NonNull Duration duration, 2295 @NonNull VibrationParameter targetParameter1, 2296 @NonNull VibrationParameter targetParameter2) { 2297 Preconditions.checkNotNull(duration, "Duration is null"); 2298 checkVibrationParameter(targetParameter1, "targetParameter1"); 2299 checkVibrationParameter(targetParameter2, "targetParameter2"); 2300 Preconditions.checkArgument( 2301 !Objects.equals(targetParameter1.getClass(), targetParameter2.getClass()), 2302 "Parameter arguments must specify different parameter types"); 2303 float amplitude = extractTargetAmplitude(targetParameter1, targetParameter2); 2304 float frequencyHz = extractTargetFrequency(targetParameter1, targetParameter2); 2305 addTransitionSegment(duration, amplitude, frequencyHz); 2306 return this; 2307 } 2308 2309 /** 2310 * Add a duration to sustain the last vibration parameters of this waveform. 2311 * 2312 * <p>The duration represents how long the vibrator should sustain the last set of 2313 * parameters provided to this builder. 2314 * 2315 * @param duration The length of time the last values should be sustained by the vibrator. 2316 * Value must be >= 1ms. 2317 * @return This {@link WaveformBuilder} object to enable adding multiple transitions in 2318 * chain. 2319 * @hide 2320 */ 2321 @TestApi 2322 @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created. 2323 @NonNull 2324 public WaveformBuilder addSustain(@NonNull Duration duration) { 2325 int durationMs = (int) duration.toMillis(); 2326 Preconditions.checkArgument(durationMs >= 1, "Sustain duration must be >= 1ms"); 2327 mSegments.add(new StepSegment(mLastAmplitude, mLastFrequencyHz, durationMs)); 2328 return this; 2329 } 2330 2331 /** 2332 * Build the waveform as a single {@link VibrationEffect}. 2333 * 2334 * <p>The {@link WaveformBuilder} object is still valid after this call, so you can 2335 * continue adding more primitives to it and generating more {@link VibrationEffect}s by 2336 * calling this method again. 2337 * 2338 * @return The {@link VibrationEffect} resulting from the list of transitions. 2339 * @hide 2340 */ 2341 @TestApi 2342 @NonNull 2343 public VibrationEffect build() { 2344 if (mSegments.isEmpty()) { 2345 throw new IllegalStateException( 2346 "WaveformBuilder must have at least one transition to build."); 2347 } 2348 VibrationEffect effect = new Composed(mSegments, /* repeatIndex= */ -1); 2349 effect.validate(); 2350 return effect; 2351 } 2352 2353 private void checkVibrationParameter(@NonNull VibrationParameter vibrationParameter, 2354 String paramName) { 2355 Preconditions.checkNotNull(vibrationParameter, "%s is null", paramName); 2356 Preconditions.checkArgument( 2357 (vibrationParameter instanceof AmplitudeVibrationParameter) 2358 || (vibrationParameter instanceof FrequencyVibrationParameter), 2359 "%s is a unknown parameter", paramName); 2360 } 2361 2362 private float extractTargetAmplitude(@Nullable VibrationParameter target1, 2363 @Nullable VibrationParameter target2) { 2364 if (target2 instanceof AmplitudeVibrationParameter) { 2365 return ((AmplitudeVibrationParameter) target2).amplitude; 2366 } 2367 if (target1 instanceof AmplitudeVibrationParameter) { 2368 return ((AmplitudeVibrationParameter) target1).amplitude; 2369 } 2370 return mLastAmplitude; 2371 } 2372 2373 private float extractTargetFrequency(@Nullable VibrationParameter target1, 2374 @Nullable VibrationParameter target2) { 2375 if (target2 instanceof FrequencyVibrationParameter) { 2376 return ((FrequencyVibrationParameter) target2).frequencyHz; 2377 } 2378 if (target1 instanceof FrequencyVibrationParameter) { 2379 return ((FrequencyVibrationParameter) target1).frequencyHz; 2380 } 2381 return mLastFrequencyHz; 2382 } 2383 2384 private void addTransitionSegment(Duration duration, float targetAmplitude, 2385 float targetFrequency) { 2386 Preconditions.checkNotNull(duration, "Duration is null"); 2387 Preconditions.checkArgument(!duration.isNegative(), 2388 "Transition duration must be non-negative"); 2389 int durationMs = (int) duration.toMillis(); 2390 2391 // Ignore transitions with zero duration, but keep values for next additions. 2392 if (durationMs > 0) { 2393 if ((Math.abs(mLastAmplitude - targetAmplitude) < EPSILON) 2394 && (Math.abs(mLastFrequencyHz - targetFrequency) < EPSILON)) { 2395 // No value is changing, this can be best represented by a step segment. 2396 mSegments.add(new StepSegment(targetAmplitude, targetFrequency, durationMs)); 2397 } else { 2398 mSegments.add(new RampSegment(mLastAmplitude, targetAmplitude, 2399 mLastFrequencyHz, targetFrequency, durationMs)); 2400 } 2401 } 2402 2403 mLastAmplitude = targetAmplitude; 2404 mLastFrequencyHz = targetFrequency; 2405 } 2406 } 2407 2408 /** 2409 * A representation of a single vibration parameter. 2410 * 2411 * <p>This is to describe a waveform haptic effect, which consists of one or more timed 2412 * transitions to a new set of {@link VibrationParameter}s. 2413 * 2414 * <p>Examples of concrete parameters are the vibration amplitude or frequency. 2415 * 2416 * @see VibrationEffect.WaveformBuilder 2417 * @hide 2418 */ 2419 @TestApi 2420 @SuppressWarnings("UserHandleName") // This is not a regular set of parameters, no *Params. 2421 public static class VibrationParameter { 2422 VibrationParameter() { 2423 } 2424 2425 /** 2426 * The target vibration amplitude. 2427 * 2428 * @param amplitude The amplitude value, between 0 and 1, inclusive, where 0 represents the 2429 * vibrator turned off and 1 represents the maximum amplitude the vibrator 2430 * can reach across all supported frequencies. 2431 * @return The {@link VibrationParameter} instance that represents given amplitude. 2432 * @hide 2433 */ 2434 @TestApi 2435 @NonNull 2436 public static VibrationParameter targetAmplitude( 2437 @FloatRange(from = 0, to = 1) float amplitude) { 2438 return new AmplitudeVibrationParameter(amplitude); 2439 } 2440 2441 /** 2442 * The target vibration frequency. 2443 * 2444 * @param frequencyHz The frequency value, in hertz. 2445 * @return The {@link VibrationParameter} instance that represents given frequency. 2446 * @hide 2447 */ 2448 @TestApi 2449 @NonNull 2450 public static VibrationParameter targetFrequency(@FloatRange(from = 1) float frequencyHz) { 2451 return new FrequencyVibrationParameter(frequencyHz); 2452 } 2453 } 2454 2455 /** The vibration amplitude, represented by a value in [0,1]. */ 2456 private static final class AmplitudeVibrationParameter extends VibrationParameter { 2457 public final float amplitude; 2458 2459 AmplitudeVibrationParameter(float amplitude) { 2460 Preconditions.checkArgument((amplitude >= 0) && (amplitude <= 1), 2461 "Amplitude must be within [0,1]"); 2462 this.amplitude = amplitude; 2463 } 2464 } 2465 2466 /** The vibration frequency, in hertz, or zero to represent undefined frequency. */ 2467 private static final class FrequencyVibrationParameter extends VibrationParameter { 2468 public final float frequencyHz; 2469 2470 FrequencyVibrationParameter(float frequencyHz) { 2471 Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1"); 2472 Preconditions.checkArgument(Float.isFinite(frequencyHz), "Frequency must be finite"); 2473 this.frequencyHz = frequencyHz; 2474 } 2475 } 2476 2477 @NonNull 2478 public static final Parcelable.Creator<VibrationEffect> CREATOR = 2479 new Parcelable.Creator<VibrationEffect>() { 2480 @Override 2481 public VibrationEffect createFromParcel(Parcel in) { 2482 switch (in.readInt()) { 2483 case PARCEL_TOKEN_COMPOSED: 2484 return new Composed(in); 2485 case PARCEL_TOKEN_VENDOR_EFFECT: 2486 if (Flags.vendorVibrationEffects()) { 2487 return new VendorEffect(in); 2488 } // else fall through 2489 default: 2490 throw new IllegalStateException( 2491 "Unexpected vibration effect type token in parcel."); 2492 } 2493 } 2494 @Override 2495 public VibrationEffect[] newArray(int size) { 2496 return new VibrationEffect[size]; 2497 } 2498 }; 2499 } 2500