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 android.annotation.FloatRange; 20 import android.annotation.IntDef; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.TestApi; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.ContentResolver; 27 import android.content.Context; 28 import android.hardware.vibrator.V1_0.EffectStrength; 29 import android.hardware.vibrator.V1_3.Effect; 30 import android.net.Uri; 31 import android.os.Vibrator; 32 import android.os.vibrator.PrebakedSegment; 33 import android.os.vibrator.PrimitiveSegment; 34 import android.os.vibrator.RampSegment; 35 import android.os.vibrator.StepSegment; 36 import android.os.vibrator.VibrationEffectSegment; 37 import android.util.MathUtils; 38 39 import com.android.internal.util.Preconditions; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.time.Duration; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.List; 47 import java.util.Objects; 48 49 /** 50 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}. 51 * 52 * <p>These effects may be any number of things, from single shot vibrations to complex waveforms. 53 */ 54 public abstract class VibrationEffect implements Parcelable { 55 // Stevens' coefficient to scale the perceived vibration intensity. 56 private static final float SCALE_GAMMA = 0.65f; 57 // If a vibration is playing for longer than 1s, it's probably not haptic feedback 58 private static final long MAX_HAPTIC_FEEDBACK_DURATION = 1000; 59 // If a vibration is playing more than 3 constants, it's probably not haptic feedback 60 private static final long MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE = 3; 61 62 /** 63 * The default vibration strength of the device. 64 */ 65 public static final int DEFAULT_AMPLITUDE = -1; 66 67 /** 68 * The maximum amplitude value 69 * @hide 70 */ 71 public static final int MAX_AMPLITUDE = 255; 72 73 /** 74 * A click effect. Use this effect as a baseline, as it's the most common type of click effect. 75 */ 76 public static final int EFFECT_CLICK = Effect.CLICK; 77 78 /** 79 * A double click effect. 80 */ 81 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK; 82 83 /** 84 * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}. 85 */ 86 public static final int EFFECT_TICK = Effect.TICK; 87 88 /** 89 * A thud effect. 90 * @see #get(int) 91 * @hide 92 */ 93 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 94 @TestApi 95 public static final int EFFECT_THUD = Effect.THUD; 96 97 /** 98 * A pop effect. 99 * @see #get(int) 100 * @hide 101 */ 102 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 103 @TestApi 104 public static final int EFFECT_POP = Effect.POP; 105 106 /** 107 * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}. 108 */ 109 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK; 110 111 /** 112 * A texture effect meant to replicate soft ticks. 113 * 114 * <p>Unlike normal effects, texture effects are meant to be called repeatedly, generally in 115 * response to some motion, in order to replicate the feeling of some texture underneath the 116 * user's fingers. 117 * 118 * @see #get(int) 119 * @hide 120 */ 121 @TestApi 122 public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK; 123 124 /** {@hide} */ 125 @TestApi 126 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT; 127 128 /** {@hide} */ 129 @TestApi 130 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM; 131 132 /** {@hide} */ 133 @TestApi 134 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG; 135 136 /** 137 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a 138 * pattern that can be played as a ringtone with any audio, depending on the device. 139 * 140 * @see #get(Uri, Context) 141 * @hide 142 */ 143 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 144 @TestApi 145 public static final int[] RINGTONES = { 146 Effect.RINGTONE_1, 147 Effect.RINGTONE_2, 148 Effect.RINGTONE_3, 149 Effect.RINGTONE_4, 150 Effect.RINGTONE_5, 151 Effect.RINGTONE_6, 152 Effect.RINGTONE_7, 153 Effect.RINGTONE_8, 154 Effect.RINGTONE_9, 155 Effect.RINGTONE_10, 156 Effect.RINGTONE_11, 157 Effect.RINGTONE_12, 158 Effect.RINGTONE_13, 159 Effect.RINGTONE_14, 160 Effect.RINGTONE_15 161 }; 162 163 /** @hide */ 164 @IntDef(prefix = { "EFFECT_" }, value = { 165 EFFECT_TICK, 166 EFFECT_CLICK, 167 EFFECT_HEAVY_CLICK, 168 EFFECT_DOUBLE_CLICK, 169 }) 170 @Retention(RetentionPolicy.SOURCE) 171 public @interface EffectType {} 172 173 /** @hide to prevent subclassing from outside of the framework */ VibrationEffect()174 public VibrationEffect() { } 175 176 /** 177 * Create a one shot vibration. 178 * 179 * <p>One shot vibrations will vibrate constantly for the specified period of time at the 180 * specified amplitude, and then stop. 181 * 182 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number. 183 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or 184 * {@link #DEFAULT_AMPLITUDE}. 185 * 186 * @return The desired effect. 187 */ createOneShot(long milliseconds, int amplitude)188 public static VibrationEffect createOneShot(long milliseconds, int amplitude) { 189 if (amplitude == 0) { 190 throw new IllegalArgumentException( 191 "amplitude must either be DEFAULT_AMPLITUDE, " 192 + "or between 1 and 255 inclusive (amplitude=" + amplitude + ")"); 193 } 194 return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */); 195 } 196 197 /** 198 * Create a waveform vibration, using only off/on transitions at the provided time intervals, 199 * and potentially repeating. 200 * 201 * <p>In effect, the timings array represents the number of milliseconds <em>before</em> turning 202 * the vibrator on, followed by the number of milliseconds to keep the vibrator on, then 203 * the number of milliseconds turned off, and so on. Consequently, the first timing value will 204 * often be 0, so that the effect will start vibrating immediately. 205 * 206 * <p>This method is equivalent to calling {@link #createWaveform(long[], int[], int)} with 207 * corresponding amplitude values alternating between 0 and {@link #DEFAULT_AMPLITUDE}, 208 * beginning with 0. 209 * 210 * <p>To cause the pattern to repeat, pass the index into the timings array at which to start 211 * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely 212 * and should be cancelled via {@link Vibrator#cancel()}. 213 * 214 * @param timings The pattern of alternating on-off timings, starting with an 'off' timing, and 215 * representing the length of time to sustain the individual item (not 216 * cumulative). 217 * @param repeat The index into the timings array at which to repeat, or -1 if you don't 218 * want to repeat indefinitely. 219 * 220 * @return The desired effect. 221 */ createWaveform(long[] timings, int repeat)222 public static VibrationEffect createWaveform(long[] timings, int repeat) { 223 int[] amplitudes = new int[timings.length]; 224 for (int i = 0; i < (timings.length / 2); i++) { 225 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE; 226 } 227 return createWaveform(timings, amplitudes, repeat); 228 } 229 230 /** 231 * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on" 232 * vibration components) that is equivalent to this VibrationEffect. 233 * 234 * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible 235 * into an equivalent vibration pattern with this method. It is not guaranteed that an effect 236 * created with other means becomes converted into an equivalent legacy vibration pattern, even 237 * if it has an equivalent vibration pattern. If this method is unable to create an equivalent 238 * vibration pattern for such effects, it will return {@code null}. 239 * 240 * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any 241 * form of repeating behavior, regardless of how the effect was created. For repeating effects, 242 * the method will always return {@code null}. 243 * 244 * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if 245 * the method successfully derived a vibration pattern equivalent to the effect 246 * (this will always be the case if the effect was created via 247 * {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns 248 * {@code null}. 249 * @hide 250 */ 251 @TestApi 252 @Nullable computeCreateWaveformOffOnTimingsOrNull()253 public abstract long[] computeCreateWaveformOffOnTimingsOrNull(); 254 255 /** 256 * Create a waveform vibration. 257 * 258 * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs, 259 * provided in separate arrays. For each pair, the value in the amplitude array determines 260 * the strength of the vibration and the value in the timing array determines how long it 261 * vibrates for, in milliseconds. 262 * 263 * <p>To cause the pattern to repeat, pass the index into the timings array at which to start 264 * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely 265 * and should be cancelled via {@link Vibrator#cancel()}. 266 * 267 * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing 268 * values of 0 will cause the pair to be ignored. 269 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values 270 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An 271 * amplitude value of 0 implies the motor is off. 272 * @param repeat The index into the timings array at which to repeat, or -1 if you don't 273 * want to repeat indefinitely. 274 * 275 * @return The desired effect. 276 */ createWaveform(long[] timings, int[] amplitudes, int repeat)277 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { 278 if (timings.length != amplitudes.length) { 279 throw new IllegalArgumentException( 280 "timing and amplitude arrays must be of equal length" 281 + " (timings.length=" + timings.length 282 + ", amplitudes.length=" + amplitudes.length + ")"); 283 } 284 List<StepSegment> segments = new ArrayList<>(); 285 for (int i = 0; i < timings.length; i++) { 286 float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE 287 ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE; 288 segments.add(new StepSegment(parsedAmplitude, /* frequencyHz= */ 0, (int) timings[i])); 289 } 290 VibrationEffect effect = new Composed(segments, repeat); 291 effect.validate(); 292 return effect; 293 } 294 295 /** 296 * Create a predefined vibration effect. 297 * 298 * <p>Predefined effects are a set of common vibration effects that should be identical, 299 * regardless of the app they come from, in order to provide a cohesive experience for users 300 * across the entire device. They also may be custom tailored to the device hardware in order to 301 * provide a better experience than you could otherwise build using the generic building 302 * blocks. 303 * 304 * <p>This will fallback to a generic pattern if one exists and there does not exist a 305 * hardware-specific implementation of the effect. 306 * 307 * @param effectId The ID of the effect to perform: 308 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 309 * 310 * @return The desired effect. 311 */ 312 @NonNull createPredefined(@ffectType int effectId)313 public static VibrationEffect createPredefined(@EffectType int effectId) { 314 return get(effectId, true); 315 } 316 317 /** 318 * Get a predefined vibration effect. 319 * 320 * <p>Predefined effects are a set of common vibration effects that should be identical, 321 * regardless of the app they come from, in order to provide a cohesive experience for users 322 * across the entire device. They also may be custom tailored to the device hardware in order to 323 * provide a better experience than you could otherwise build using the generic building 324 * blocks. 325 * 326 * <p>This will fallback to a generic pattern if one exists and there does not exist a 327 * hardware-specific implementation of the effect. 328 * 329 * @param effectId The ID of the effect to perform: 330 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 331 * 332 * @return The desired effect. 333 * @hide 334 */ 335 @TestApi get(int effectId)336 public static VibrationEffect get(int effectId) { 337 return get(effectId, true); 338 } 339 340 /** 341 * Get a predefined vibration effect. 342 * 343 * <p>Predefined effects are a set of common vibration effects that should be identical, 344 * regardless of the app they come from, in order to provide a cohesive experience for users 345 * across the entire device. They also may be custom tailored to the device hardware in order to 346 * provide a better experience than you could otherwise build using the generic building 347 * blocks. 348 * 349 * <p>Some effects you may only want to play if there's a hardware specific implementation 350 * because they may, for example, be too disruptive to the user without tuning. The 351 * {@code fallback} parameter allows you to decide whether you want to fallback to the generic 352 * implementation or only play if there's a tuned, hardware specific one available. 353 * 354 * @param effectId The ID of the effect to perform: 355 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 356 * @param fallback Whether to fallback to a generic pattern if a hardware specific 357 * implementation doesn't exist. 358 * 359 * @return The desired effect. 360 * @hide 361 */ 362 @TestApi get(int effectId, boolean fallback)363 public static VibrationEffect get(int effectId, boolean fallback) { 364 VibrationEffect effect = new Composed( 365 new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM)); 366 effect.validate(); 367 return effect; 368 } 369 370 /** 371 * Get a predefined vibration effect associated with a given URI. 372 * 373 * <p>Predefined effects are a set of common vibration effects that should be identical, 374 * regardless of the app they come from, in order to provide a cohesive experience for users 375 * across the entire device. They also may be custom tailored to the device hardware in order to 376 * provide a better experience than you could otherwise build using the generic building 377 * blocks. 378 * 379 * @param uri The URI associated with the haptic effect. 380 * @param context The context used to get the URI to haptic effect association. 381 * 382 * @return The desired effect, or {@code null} if there's no associated effect. 383 * 384 * @hide 385 */ 386 @TestApi 387 @Nullable get(Uri uri, Context context)388 public static VibrationEffect get(Uri uri, Context context) { 389 String[] uris = context.getResources().getStringArray( 390 com.android.internal.R.array.config_ringtoneEffectUris); 391 392 // Skip doing any IPC if we don't have any effects configured. 393 if (uris.length == 0) { 394 return null; 395 } 396 397 final ContentResolver cr = context.getContentResolver(); 398 Uri uncanonicalUri = cr.uncanonicalize(uri); 399 if (uncanonicalUri == null) { 400 // If we already had an uncanonical URI, it's possible we'll get null back here. In 401 // this case, just use the URI as passed in since it wasn't canonicalized in the first 402 // place. 403 uncanonicalUri = uri; 404 } 405 406 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) { 407 if (uris[i] == null) { 408 continue; 409 } 410 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i])); 411 if (mappedUri == null) { 412 continue; 413 } 414 if (mappedUri.equals(uncanonicalUri)) { 415 return get(RINGTONES[i]); 416 } 417 } 418 return null; 419 } 420 421 /** 422 * Start composing a haptic effect. 423 * 424 * @see VibrationEffect.Composition 425 */ 426 @NonNull startComposition()427 public static Composition startComposition() { 428 return new Composition(); 429 } 430 431 /** 432 * Start building a waveform vibration. 433 * 434 * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing 435 * control over vibration amplitude and frequency via smooth transitions between values. 436 * 437 * <p>The waveform will start the first transition from the vibrator off state, with the 438 * resonant frequency by default. To provide an initial state, use 439 * {@link #startWaveform(VibrationEffect.VibrationParameter)}. 440 * 441 * @see VibrationEffect.WaveformBuilder 442 * @hide 443 */ 444 @TestApi 445 @NonNull startWaveform()446 public static WaveformBuilder startWaveform() { 447 return new WaveformBuilder(); 448 } 449 450 /** 451 * Start building a waveform vibration with an initial state specified by a 452 * {@link VibrationEffect.VibrationParameter}. 453 * 454 * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing 455 * control over vibration amplitude and frequency via smooth transitions between values. 456 * 457 * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be 458 * applied at the beginning of the vibration. 459 * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. 460 * 461 * @see VibrationEffect.WaveformBuilder 462 * @hide 463 */ 464 @TestApi 465 @NonNull startWaveform(@onNull VibrationParameter initialParameter)466 public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) { 467 WaveformBuilder builder = startWaveform(); 468 builder.addTransition(Duration.ZERO, initialParameter); 469 return builder; 470 } 471 472 /** 473 * Start building a waveform vibration with an initial state specified by two 474 * {@link VibrationEffect.VibrationParameter VibrationParameters}. 475 * 476 * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing 477 * control over vibration amplitude and frequency via smooth transitions between values. 478 * 479 * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be 480 * applied at the beginning of the vibration. 481 * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be 482 * applied at the beginning of the vibration, must be a different type 483 * of parameter than the one specified by the first argument. 484 * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. 485 * 486 * @see VibrationEffect.WaveformBuilder 487 * @hide 488 */ 489 @TestApi 490 @NonNull startWaveform(@onNull VibrationParameter initialParameter1, @NonNull VibrationParameter initialParameter2)491 public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1, 492 @NonNull VibrationParameter initialParameter2) { 493 WaveformBuilder builder = startWaveform(); 494 builder.addTransition(Duration.ZERO, initialParameter1, initialParameter2); 495 return builder; 496 } 497 498 @Override describeContents()499 public int describeContents() { 500 return 0; 501 } 502 503 /** @hide */ validate()504 public abstract void validate(); 505 506 /** 507 * Gets the estimated duration of the vibration in milliseconds. 508 * 509 * <p>For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this 510 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where 511 * the length is device and potentially run-time dependent), this returns -1. 512 * 513 * @hide 514 */ 515 @TestApi getDuration()516 public abstract long getDuration(); 517 518 /** 519 * Checks if a given {@link Vibrator} can play this effect as intended. 520 * 521 * <p>See @link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more information 522 * about what counts as supported by a vibrator, and what counts as not. 523 * 524 * @hide 525 */ areVibrationFeaturesSupported(@onNull Vibrator vibrator)526 public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator); 527 528 /** 529 * Returns true if this effect could represent a touch haptic feedback. 530 * 531 * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified 532 * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN, 533 * then this method will be used to classify the most common use case and make sure they are 534 * covered by the user settings for "Touch feedback". 535 * 536 * @hide 537 */ isHapticFeedbackCandidate()538 public boolean isHapticFeedbackCandidate() { 539 return false; 540 } 541 542 /** 543 * Resolve default values into integer amplitude numbers. 544 * 545 * @param defaultAmplitude the default amplitude to apply, must be between 0 and 546 * MAX_AMPLITUDE 547 * @return this if amplitude value is already set, or a copy of this effect with given default 548 * amplitude otherwise 549 * 550 * @hide 551 */ resolve(int defaultAmplitude)552 public abstract <T extends VibrationEffect> T resolve(int defaultAmplitude); 553 554 /** 555 * Scale the vibration effect intensity with the given constraints. 556 * 557 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will 558 * scale down the intensity, values larger than 1 will scale up 559 * @return this if there is no scaling to be done, or a copy of this effect with scaled 560 * vibration intensity otherwise 561 * 562 * @hide 563 */ scale(float scaleFactor)564 public abstract <T extends VibrationEffect> T scale(float scaleFactor); 565 566 /** 567 * Applies given effect strength to prebaked effects represented by one of 568 * VibrationEffect.EFFECT_*. 569 * 570 * @param effectStrength new effect strength to be applied, one of 571 * VibrationEffect.EFFECT_STRENGTH_*. 572 * @return this if there is no change to this effect, or a copy of this effect with applied 573 * effect strength otherwise. 574 * @hide 575 */ applyEffectStrength(int effectStrength)576 public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) { 577 return (T) this; 578 } 579 580 /** 581 * Scale given vibration intensity by the given factor. 582 * 583 * @param intensity relative intensity of the effect, must be between 0 and 1 584 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will 585 * scale down the intensity, values larger than 1 will scale up 586 * @hide 587 */ scale(float intensity, float scaleFactor)588 public static float scale(float intensity, float scaleFactor) { 589 // Applying gamma correction to the scale factor, which is the same as encoding the input 590 // value, scaling it, then decoding the scaled value. 591 float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA); 592 593 if (scaleFactor <= 1) { 594 // Scale down is simply a gamma corrected application of scaleFactor to the intensity. 595 // Scale up requires a different curve to ensure the intensity will not become > 1. 596 return intensity * scale; 597 } 598 599 // Apply the scale factor a few more times to make the ramp curve closer to the raw scale. 600 float extraScale = MathUtils.pow(scaleFactor, 4f - scaleFactor); 601 float x = intensity * scale * extraScale; 602 float maxX = scale * extraScale; // scaled x for intensity == 1 603 604 float expX = MathUtils.exp(x); 605 float expMaxX = MathUtils.exp(maxX); 606 607 // Using f = tanh as the scale up function so the max value will converge. 608 // a = 1/f(maxX), used to scale f so that a*f(maxX) = 1 (the value will converge to 1). 609 float a = (expMaxX + 1f) / (expMaxX - 1f); 610 float fx = (expX - 1f) / (expX + 1f); 611 612 return MathUtils.constrain(a * fx, 0f, 1f); 613 } 614 615 /** @hide */ effectIdToString(int effectId)616 public static String effectIdToString(int effectId) { 617 switch (effectId) { 618 case EFFECT_CLICK: 619 return "CLICK"; 620 case EFFECT_TICK: 621 return "TICK"; 622 case EFFECT_HEAVY_CLICK: 623 return "HEAVY_CLICK"; 624 case EFFECT_DOUBLE_CLICK: 625 return "DOUBLE_CLICK"; 626 case EFFECT_POP: 627 return "POP"; 628 case EFFECT_THUD: 629 return "THUD"; 630 case EFFECT_TEXTURE_TICK: 631 return "TEXTURE_TICK"; 632 default: 633 return Integer.toString(effectId); 634 } 635 } 636 637 /** @hide */ effectStrengthToString(int effectStrength)638 public static String effectStrengthToString(int effectStrength) { 639 switch (effectStrength) { 640 case EFFECT_STRENGTH_LIGHT: 641 return "LIGHT"; 642 case EFFECT_STRENGTH_MEDIUM: 643 return "MEDIUM"; 644 case EFFECT_STRENGTH_STRONG: 645 return "STRONG"; 646 default: 647 return Integer.toString(effectStrength); 648 } 649 } 650 651 /** 652 * Implementation of {@link VibrationEffect} described by a composition of one or more 653 * {@link VibrationEffectSegment}, with an optional index to represent repeating effects. 654 * 655 * @hide 656 */ 657 @TestApi 658 public static final class Composed extends VibrationEffect { 659 private final ArrayList<VibrationEffectSegment> mSegments; 660 private final int mRepeatIndex; 661 Composed(@onNull Parcel in)662 Composed(@NonNull Parcel in) { 663 this(in.readArrayList(VibrationEffectSegment.class.getClassLoader(), android.os.vibrator.VibrationEffectSegment.class), in.readInt()); 664 } 665 Composed(@onNull VibrationEffectSegment segment)666 Composed(@NonNull VibrationEffectSegment segment) { 667 this(Arrays.asList(segment), /* repeatIndex= */ -1); 668 } 669 670 /** @hide */ Composed(@onNull List<? extends VibrationEffectSegment> segments, int repeatIndex)671 public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) { 672 super(); 673 mSegments = new ArrayList<>(segments); 674 mRepeatIndex = repeatIndex; 675 } 676 677 @NonNull getSegments()678 public List<VibrationEffectSegment> getSegments() { 679 return mSegments; 680 } 681 getRepeatIndex()682 public int getRepeatIndex() { 683 return mRepeatIndex; 684 } 685 686 /** @hide */ 687 @Override 688 @Nullable computeCreateWaveformOffOnTimingsOrNull()689 public long[] computeCreateWaveformOffOnTimingsOrNull() { 690 if (getRepeatIndex() >= 0) { 691 // Repeating effects cannot be fully represented as a long[] legacy pattern. 692 return null; 693 } 694 695 List<VibrationEffectSegment> segments = getSegments(); 696 697 // The maximum possible size of the final pattern is 1 plus the number of segments in 698 // the original effect. This is because we will add an empty "off" segment at the 699 // start of the pattern if the first segment of the original effect is an "on" segment. 700 // (because the legacy patterns start with an "off" pattern). Other than this one case, 701 // we will add the durations of back-to-back segments of similar amplitudes (amplitudes 702 // that are all "on" or "off") and create a pattern entry for the total duration, which 703 // will not take more number pattern entries than the number of segments processed. 704 long[] patternBuffer = new long[segments.size() + 1]; 705 int patternIndex = 0; 706 707 for (int i = 0; i < segments.size(); i++) { 708 StepSegment stepSegment = 709 castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i)); 710 if (stepSegment == null) { 711 // This means that there is 1 or more segments of this effect that is/are not a 712 // possible component of a legacy vibration pattern. Thus, the VibrationEffect 713 // does not have any equivalent legacy vibration pattern. 714 return null; 715 } 716 717 boolean isSegmentOff = stepSegment.getAmplitude() == 0; 718 // Even pattern indices are "off", and odd pattern indices are "on" 719 boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0; 720 if (isSegmentOff != isCurrentPatternIndexOff) { 721 // Move the pattern index one step ahead, so that the current segment's 722 // "off"/"on" property matches that of the index's 723 ++patternIndex; 724 } 725 patternBuffer[patternIndex] += stepSegment.getDuration(); 726 } 727 728 return Arrays.copyOf(patternBuffer, patternIndex + 1); 729 } 730 731 /** @hide */ 732 @Override validate()733 public void validate() { 734 int segmentCount = mSegments.size(); 735 boolean hasNonZeroDuration = false; 736 for (int i = 0; i < segmentCount; i++) { 737 VibrationEffectSegment segment = mSegments.get(i); 738 segment.validate(); 739 // A segment with unknown duration = -1 still counts as a non-zero duration. 740 hasNonZeroDuration |= segment.getDuration() != 0; 741 } 742 if (!hasNonZeroDuration) { 743 throw new IllegalArgumentException("at least one timing must be non-zero" 744 + " (segments=" + mSegments + ")"); 745 } 746 if (mRepeatIndex != -1) { 747 Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1, 748 "repeat index must be within the bounds of the segments (segments.length=" 749 + segmentCount + ", index=" + mRepeatIndex + ")"); 750 } 751 } 752 753 @Override getDuration()754 public long getDuration() { 755 if (mRepeatIndex >= 0) { 756 return Long.MAX_VALUE; 757 } 758 int segmentCount = mSegments.size(); 759 long totalDuration = 0; 760 for (int i = 0; i < segmentCount; i++) { 761 long segmentDuration = mSegments.get(i).getDuration(); 762 if (segmentDuration < 0) { 763 return segmentDuration; 764 } 765 totalDuration += segmentDuration; 766 } 767 return totalDuration; 768 } 769 770 /** @hide */ 771 @Override areVibrationFeaturesSupported(@onNull Vibrator vibrator)772 public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) { 773 for (VibrationEffectSegment segment : mSegments) { 774 if (!segment.areVibrationFeaturesSupported(vibrator)) { 775 return false; 776 } 777 } 778 return true; 779 } 780 781 /** @hide */ 782 @Override isHapticFeedbackCandidate()783 public boolean isHapticFeedbackCandidate() { 784 long totalDuration = getDuration(); 785 if (totalDuration > MAX_HAPTIC_FEEDBACK_DURATION) { 786 // Vibration duration is known and is longer than the max duration used to classify 787 // haptic feedbacks (or repeating indefinitely with duration == Long.MAX_VALUE). 788 return false; 789 } 790 int segmentCount = mSegments.size(); 791 if (segmentCount > MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE) { 792 // Vibration has some prebaked or primitive constants, it should be limited to the 793 // max composition size used to classify haptic feedbacks. 794 return false; 795 } 796 totalDuration = 0; 797 for (int i = 0; i < segmentCount; i++) { 798 if (!mSegments.get(i).isHapticFeedbackCandidate()) { 799 // There is at least one segment that is not a candidate for a haptic feedback. 800 return false; 801 } 802 long segmentDuration = mSegments.get(i).getDuration(); 803 if (segmentDuration > 0) { 804 totalDuration += segmentDuration; 805 } 806 } 807 // Vibration might still have some ramp or step segments, check the known duration. 808 return totalDuration <= MAX_HAPTIC_FEEDBACK_DURATION; 809 } 810 811 /** @hide */ 812 @NonNull 813 @Override resolve(int defaultAmplitude)814 public Composed resolve(int defaultAmplitude) { 815 int segmentCount = mSegments.size(); 816 ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount); 817 for (int i = 0; i < segmentCount; i++) { 818 resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude)); 819 } 820 if (resolvedSegments.equals(mSegments)) { 821 return this; 822 } 823 Composed resolved = new Composed(resolvedSegments, mRepeatIndex); 824 resolved.validate(); 825 return resolved; 826 } 827 828 /** @hide */ 829 @NonNull 830 @Override scale(float scaleFactor)831 public Composed scale(float scaleFactor) { 832 int segmentCount = mSegments.size(); 833 ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount); 834 for (int i = 0; i < segmentCount; i++) { 835 scaledSegments.add(mSegments.get(i).scale(scaleFactor)); 836 } 837 if (scaledSegments.equals(mSegments)) { 838 return this; 839 } 840 Composed scaled = new Composed(scaledSegments, mRepeatIndex); 841 scaled.validate(); 842 return scaled; 843 } 844 845 /** @hide */ 846 @NonNull 847 @Override applyEffectStrength(int effectStrength)848 public Composed applyEffectStrength(int effectStrength) { 849 int segmentCount = mSegments.size(); 850 ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount); 851 for (int i = 0; i < segmentCount; i++) { 852 scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength)); 853 } 854 if (scaledSegments.equals(mSegments)) { 855 return this; 856 } 857 Composed scaled = new Composed(scaledSegments, mRepeatIndex); 858 scaled.validate(); 859 return scaled; 860 } 861 862 @Override equals(@ullable Object o)863 public boolean equals(@Nullable Object o) { 864 if (!(o instanceof Composed)) { 865 return false; 866 } 867 Composed other = (Composed) o; 868 return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex; 869 } 870 871 @Override hashCode()872 public int hashCode() { 873 return Objects.hash(mSegments, mRepeatIndex); 874 } 875 876 @Override toString()877 public String toString() { 878 return "Composed{segments=" + mSegments 879 + ", repeat=" + mRepeatIndex 880 + "}"; 881 } 882 883 @Override describeContents()884 public int describeContents() { 885 return 0; 886 } 887 888 @Override writeToParcel(@onNull Parcel out, int flags)889 public void writeToParcel(@NonNull Parcel out, int flags) { 890 out.writeList(mSegments); 891 out.writeInt(mRepeatIndex); 892 } 893 894 @NonNull 895 public static final Creator<Composed> CREATOR = 896 new Creator<Composed>() { 897 @Override 898 public Composed createFromParcel(Parcel in) { 899 return new Composed(in); 900 } 901 902 @Override 903 public Composed[] newArray(int size) { 904 return new Composed[size]; 905 } 906 }; 907 908 /** 909 * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it, 910 * only if it can possibly be a segment for an effect created via 911 * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}. 912 */ 913 @Nullable castToValidStepSegmentForOffOnTimingsOrNull( VibrationEffectSegment segment)914 private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull( 915 VibrationEffectSegment segment) { 916 if (!(segment instanceof StepSegment)) { 917 return null; 918 } 919 920 StepSegment stepSegment = (StepSegment) segment; 921 if (stepSegment.getFrequencyHz() != 0) { 922 return null; 923 } 924 925 float amplitude = stepSegment.getAmplitude(); 926 if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) { 927 return null; 928 } 929 930 return stepSegment; 931 } 932 } 933 934 /** 935 * A composition of haptic elements that are combined to be playable as a single 936 * {@link VibrationEffect}. 937 * 938 * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and 939 * can be added to a composition to create a custom vibration effect. Here is an example of an 940 * effect that grows in intensity and then dies off, with a longer rising portion for emphasis 941 * and an extra tick 100ms after: 942 * 943 * <pre> 944 * {@code VibrationEffect effect = VibrationEffect.startComposition() 945 * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f) 946 * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f) 947 * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100) 948 * .compose();}</pre> 949 * 950 * <p>When choosing to play a composed effect, you should check that individual components are 951 * supported by the device by using {@link Vibrator#arePrimitivesSupported}. 952 * 953 * @see VibrationEffect#startComposition() 954 */ 955 public static final class Composition { 956 /** @hide */ 957 @IntDef(prefix = { "PRIMITIVE_" }, value = { 958 PRIMITIVE_CLICK, 959 PRIMITIVE_THUD, 960 PRIMITIVE_SPIN, 961 PRIMITIVE_QUICK_RISE, 962 PRIMITIVE_SLOW_RISE, 963 PRIMITIVE_QUICK_FALL, 964 PRIMITIVE_TICK, 965 PRIMITIVE_LOW_TICK, 966 }) 967 @Retention(RetentionPolicy.SOURCE) 968 public @interface PrimitiveType { 969 } 970 971 /** 972 * Exception thrown when adding an element to a {@link Composition} that already ends in an 973 * indefinitely repeating effect. 974 * @hide 975 */ 976 @TestApi 977 public static final class UnreachableAfterRepeatingIndefinitelyException 978 extends IllegalStateException { UnreachableAfterRepeatingIndefinitelyException()979 UnreachableAfterRepeatingIndefinitelyException() { 980 super("Compositions ending in an indefinitely repeating effect can't be extended"); 981 } 982 } 983 984 /** 985 * No haptic effect. Used to generate extended delays between primitives. 986 * 987 * @hide 988 */ 989 public static final int PRIMITIVE_NOOP = 0; 990 /** 991 * This effect should produce a sharp, crisp click sensation. 992 */ 993 public static final int PRIMITIVE_CLICK = 1; 994 /** 995 * A haptic effect that simulates downwards movement with gravity. Often 996 * followed by extra energy of hitting and reverberation to augment 997 * physicality. 998 */ 999 public static final int PRIMITIVE_THUD = 2; 1000 /** 1001 * A haptic effect that simulates spinning momentum. 1002 */ 1003 public static final int PRIMITIVE_SPIN = 3; 1004 /** 1005 * A haptic effect that simulates quick upward movement against gravity. 1006 */ 1007 public static final int PRIMITIVE_QUICK_RISE = 4; 1008 /** 1009 * A haptic effect that simulates slow upward movement against gravity. 1010 */ 1011 public static final int PRIMITIVE_SLOW_RISE = 5; 1012 /** 1013 * A haptic effect that simulates quick downwards movement with gravity. 1014 */ 1015 public static final int PRIMITIVE_QUICK_FALL = 6; 1016 /** 1017 * This very short effect should produce a light crisp sensation intended 1018 * to be used repetitively for dynamic feedback. 1019 */ 1020 // Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK 1021 public static final int PRIMITIVE_TICK = 7; 1022 /** 1023 * This very short low frequency effect should produce a light crisp sensation 1024 * intended to be used repetitively for dynamic feedback. 1025 */ 1026 // Internally this maps to the HAL constant CompositePrimitive::LOW_TICK 1027 public static final int PRIMITIVE_LOW_TICK = 8; 1028 1029 1030 private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); 1031 private int mRepeatIndex = -1; 1032 Composition()1033 Composition() {} 1034 1035 /** 1036 * Adds a time duration to the current composition, during which the vibrator will be 1037 * turned off. 1038 * 1039 * @param duration The length of time the vibrator should be off. Value must be non-negative 1040 * and will be truncated to milliseconds. 1041 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1042 * 1043 * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently 1044 * ending with a repeating effect. 1045 * @hide 1046 */ 1047 @TestApi 1048 @NonNull addOffDuration(@onNull Duration duration)1049 public Composition addOffDuration(@NonNull Duration duration) { 1050 int durationMs = (int) duration.toMillis(); 1051 Preconditions.checkArgumentNonnegative(durationMs, "Off period must be non-negative"); 1052 if (durationMs > 0) { 1053 // Created a segment sustaining the zero amplitude to represent the delay. 1054 addSegment(new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, 1055 (int) duration.toMillis())); 1056 } 1057 return this; 1058 } 1059 1060 /** 1061 * Add a haptic effect to the end of the current composition. 1062 * 1063 * <p>If this effect is repeating (e.g. created by {@link VibrationEffect#createWaveform} 1064 * with a non-negative repeat index, or created by another composition that has effects 1065 * repeating indefinitely), then no more effects or primitives will be accepted by this 1066 * composition after this method. Such effects should be cancelled via 1067 * {@link Vibrator#cancel()}. 1068 * 1069 * @param effect The effect to add to the end of this composition. 1070 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1071 * 1072 * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently 1073 * ending with a repeating effect. 1074 * @hide 1075 */ 1076 @TestApi 1077 @NonNull addEffect(@onNull VibrationEffect effect)1078 public Composition addEffect(@NonNull VibrationEffect effect) { 1079 return addSegments(effect); 1080 } 1081 1082 /** 1083 * Add a haptic effect to the end of the current composition and play it on repeat, 1084 * indefinitely. 1085 * 1086 * <p>The entire effect will be played on repeat, indefinitely, after all other elements 1087 * already added to this composition are played. No more effects or primitives will be 1088 * accepted by this composition after this method. Such effects should be cancelled via 1089 * {@link Vibrator#cancel()}. 1090 * 1091 * @param effect The effect to add to the end of this composition, must be finite. 1092 * @return This {@link Composition} object to enable adding multiple elements in one chain, 1093 * although only {@link #compose()} can follow this call. 1094 * 1095 * @throws IllegalArgumentException if the given effect is already repeating indefinitely. 1096 * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently 1097 * ending with a repeating effect. 1098 * @hide 1099 */ 1100 @TestApi 1101 @NonNull repeatEffectIndefinitely(@onNull VibrationEffect effect)1102 public Composition repeatEffectIndefinitely(@NonNull VibrationEffect effect) { 1103 Preconditions.checkArgument(effect.getDuration() < Long.MAX_VALUE, 1104 "Can't repeat an indefinitely repeating effect. Consider addEffect instead."); 1105 int previousSegmentCount = mSegments.size(); 1106 addSegments(effect); 1107 // Set repeat after segments were added, since addSegments checks this index. 1108 mRepeatIndex = previousSegmentCount; 1109 return this; 1110 } 1111 1112 /** 1113 * Add a haptic primitive to the end of the current composition. 1114 * 1115 * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a 1116 * default scale applied. 1117 * 1118 * @param primitiveId The primitive to add 1119 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1120 */ 1121 @NonNull 1122 public Composition addPrimitive(@PrimitiveType int primitiveId) { 1123 return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); 1124 } 1125 1126 /** 1127 * Add a haptic primitive to the end of the current composition. 1128 * 1129 * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay. 1130 * 1131 * @param primitiveId The primitive to add 1132 * @param scale The scale to apply to the intensity of the primitive. 1133 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1134 */ 1135 @NonNull 1136 public Composition addPrimitive(@PrimitiveType int primitiveId, 1137 @FloatRange(from = 0f, to = 1f) float scale) { 1138 return addPrimitive(primitiveId, scale, /*delay*/ 0); 1139 } 1140 1141 /** 1142 * Add a haptic primitive to the end of the current composition. 1143 * 1144 * @param primitiveId The primitive to add 1145 * @param scale The scale to apply to the intensity of the primitive. 1146 * @param delay The amount of time in milliseconds to wait before playing this primitive, 1147 * starting at the time the previous element in this composition is finished. 1148 * @return This {@link Composition} object to enable adding multiple elements in one chain. 1149 */ 1150 @NonNull 1151 public Composition addPrimitive(@PrimitiveType int primitiveId, 1152 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) { 1153 PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, 1154 delay); 1155 primitive.validate(); 1156 return addSegment(primitive); 1157 } 1158 1159 private Composition addSegment(VibrationEffectSegment segment) { 1160 if (mRepeatIndex >= 0) { 1161 throw new UnreachableAfterRepeatingIndefinitelyException(); 1162 } 1163 mSegments.add(segment); 1164 return this; 1165 } 1166 addSegments(VibrationEffect effect)1167 private Composition addSegments(VibrationEffect effect) { 1168 if (mRepeatIndex >= 0) { 1169 throw new UnreachableAfterRepeatingIndefinitelyException(); 1170 } 1171 Composed composed = (Composed) effect; 1172 if (composed.getRepeatIndex() >= 0) { 1173 // Start repeating from the index relative to the composed waveform. 1174 mRepeatIndex = mSegments.size() + composed.getRepeatIndex(); 1175 } 1176 mSegments.addAll(composed.getSegments()); 1177 return this; 1178 } 1179 1180 /** 1181 * Compose all of the added primitives together into a single {@link VibrationEffect}. 1182 * 1183 * <p>The {@link Composition} object is still valid after this call, so you can continue 1184 * adding more primitives to it and generating more {@link VibrationEffect}s by calling this 1185 * method again. 1186 * 1187 * @return The {@link VibrationEffect} resulting from the composition of the primitives. 1188 */ 1189 @NonNull compose()1190 public VibrationEffect compose() { 1191 if (mSegments.isEmpty()) { 1192 throw new IllegalStateException( 1193 "Composition must have at least one element to compose."); 1194 } 1195 VibrationEffect effect = new Composed(mSegments, mRepeatIndex); 1196 effect.validate(); 1197 return effect; 1198 } 1199 1200 /** 1201 * Convert the primitive ID to a human readable string for debugging. 1202 * @param id The ID to convert 1203 * @return The ID in a human readable format. 1204 * @hide 1205 */ primitiveToString(@rimitiveType int id)1206 public static String primitiveToString(@PrimitiveType int id) { 1207 switch (id) { 1208 case PRIMITIVE_NOOP: 1209 return "PRIMITIVE_NOOP"; 1210 case PRIMITIVE_CLICK: 1211 return "PRIMITIVE_CLICK"; 1212 case PRIMITIVE_THUD: 1213 return "PRIMITIVE_THUD"; 1214 case PRIMITIVE_SPIN: 1215 return "PRIMITIVE_SPIN"; 1216 case PRIMITIVE_QUICK_RISE: 1217 return "PRIMITIVE_QUICK_RISE"; 1218 case PRIMITIVE_SLOW_RISE: 1219 return "PRIMITIVE_SLOW_RISE"; 1220 case PRIMITIVE_QUICK_FALL: 1221 return "PRIMITIVE_QUICK_FALL"; 1222 case PRIMITIVE_TICK: 1223 return "PRIMITIVE_TICK"; 1224 case PRIMITIVE_LOW_TICK: 1225 return "PRIMITIVE_LOW_TICK"; 1226 default: 1227 return Integer.toString(id); 1228 } 1229 } 1230 } 1231 1232 /** 1233 * A builder for waveform haptic effects. 1234 * 1235 * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration 1236 * parameters. These parameters can be the vibration amplitude, frequency, or both. 1237 * 1238 * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms 1239 * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms: 1240 * 1241 * <pre> 1242 * {@code import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; 1243 * import static android.os.VibrationEffect.VibrationParameter.targetFrequency; 1244 * 1245 * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60)) 1246 * .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) 1247 * .addSustain(Duration.ofMillis(200)) 1248 * .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) 1249 * .build();}</pre> 1250 * 1251 * <p>The initial state of the waveform can be set via 1252 * {@link VibrationEffect#startWaveform(VibrationParameter)} or 1253 * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial 1254 * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off, 1255 * represented by zero amplitude, at the vibrator's resonant frequency. 1256 * 1257 * <p>Repeating waveforms can be created by building the repeating block separately and adding 1258 * it to the end of a composition with 1259 * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}: 1260 * 1261 * <p>Note that physical vibration actuators have different reaction times for changing 1262 * amplitude and frequency. Durations specified here represent a timeline for the target 1263 * parameters, and quality of effects may be improved if the durations allow time for a 1264 * transition to be smoothly applied. 1265 * 1266 * <p>The following example illustrates both an initial state and a repeating section, using 1267 * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a 1268 * repeated beating effect with a rise that stretches out and a sharp finish. 1269 * 1270 * <pre> 1271 * {@code VibrationEffect patternToRepeat = VibrationEffect.startWaveform(targetAmplitude(0.2f)) 1272 * .addSustain(Duration.ofMillis(10)) 1273 * .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f)) 1274 * .addSustain(Duration.ofMillis(30)) 1275 * .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f)) 1276 * .addSustain(Duration.ofMillis(50)) 1277 * .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f)) 1278 * .build(); 1279 * 1280 * VibrationEffect effect = VibrationEffect.startComposition() 1281 * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) 1282 * .addOffDuration(Duration.ofMillis(20)) 1283 * .repeatEffectIndefinitely(patternToRepeat) 1284 * .compose();}</pre> 1285 * 1286 * <p>The amplitude step waveforms that can be created via 1287 * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with 1288 * {@link WaveformBuilder} by adding zero duration transitions: 1289 * 1290 * <pre> 1291 * {@code // These two effects are the same 1292 * VibrationEffect waveform = VibrationEffect.createWaveform( 1293 * new long[] { 10, 20, 30 }, // timings in milliseconds 1294 * new int[] { 51, 102, 204 }, // amplitudes in [0,255] 1295 * -1); // repeat index 1296 * 1297 * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f)) 1298 * .addSustain(Duration.ofMillis(10)) 1299 * .addTransition(Duration.ZERO, targetAmplitude(0.4f)) 1300 * .addSustain(Duration.ofMillis(20)) 1301 * .addTransition(Duration.ZERO, targetAmplitude(0.8f)) 1302 * .addSustain(Duration.ofMillis(30)) 1303 * .build();}</pre> 1304 * 1305 * @see VibrationEffect#startWaveform 1306 * @hide 1307 */ 1308 @TestApi 1309 public static final class WaveformBuilder { 1310 // Epsilon used for float comparison of amplitude and frequency values on transitions. 1311 private static final float EPSILON = 1e-5f; 1312 1313 private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); 1314 private float mLastAmplitude = 0f; 1315 private float mLastFrequencyHz = 0f; 1316 WaveformBuilder()1317 WaveformBuilder() {} 1318 1319 /** 1320 * Add a transition to new vibration parameter value to the end of this waveform. 1321 * 1322 * <p>The duration represents how long the vibrator should take to smoothly transition to 1323 * the new vibration parameter. If the duration is zero then the vibrator will jump to the 1324 * new value as fast as possible. 1325 * 1326 * <p>Vibration parameter values will be truncated to conform to the device capabilities 1327 * according to the {@link android.os.vibrator.VibratorFrequencyProfile}. 1328 * 1329 * @param duration The length of time this transition should take. Value must be 1330 * non-negative and will be truncated to milliseconds. 1331 * @param targetParameter The new target {@link VibrationParameter} value to be reached 1332 * after the given duration. 1333 * @return This {@link WaveformBuilder} object to enable adding multiple transitions in 1334 * chain. 1335 * @hide 1336 */ 1337 @TestApi 1338 @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created. 1339 @NonNull addTransition(@onNull Duration duration, @NonNull VibrationParameter targetParameter)1340 public WaveformBuilder addTransition(@NonNull Duration duration, 1341 @NonNull VibrationParameter targetParameter) { 1342 Preconditions.checkNotNull(duration, "Duration is null"); 1343 checkVibrationParameter(targetParameter, "targetParameter"); 1344 float amplitude = extractTargetAmplitude(targetParameter, /* target2= */ null); 1345 float frequencyHz = extractTargetFrequency(targetParameter, /* target2= */ null); 1346 addTransitionSegment(duration, amplitude, frequencyHz); 1347 return this; 1348 } 1349 1350 /** 1351 * Add a transition to new vibration parameters to the end of this waveform. 1352 * 1353 * <p>The duration represents how long the vibrator should take to smoothly transition to 1354 * the new vibration parameters. If the duration is zero then the vibrator will jump to the 1355 * new values as fast as possible. 1356 * 1357 * <p>Vibration parameters values will be truncated to conform to the device capabilities 1358 * according to the {@link android.os.vibrator.VibratorFrequencyProfile}. 1359 * 1360 * @param duration The length of time this transition should take. Value must be 1361 * non-negative and will be truncated to milliseconds. 1362 * @param targetParameter1 The first target {@link VibrationParameter} value to be reached 1363 * after the given duration. 1364 * @param targetParameter2 The second target {@link VibrationParameter} value to be reached 1365 * after the given duration, must be a different type of parameter 1366 * than the one specified by the first argument. 1367 * @return This {@link WaveformBuilder} object to enable adding multiple transitions in 1368 * chain. 1369 * @hide 1370 */ 1371 @TestApi 1372 @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created. 1373 @NonNull addTransition(@onNull Duration duration, @NonNull VibrationParameter targetParameter1, @NonNull VibrationParameter targetParameter2)1374 public WaveformBuilder addTransition(@NonNull Duration duration, 1375 @NonNull VibrationParameter targetParameter1, 1376 @NonNull VibrationParameter targetParameter2) { 1377 Preconditions.checkNotNull(duration, "Duration is null"); 1378 checkVibrationParameter(targetParameter1, "targetParameter1"); 1379 checkVibrationParameter(targetParameter2, "targetParameter2"); 1380 Preconditions.checkArgument( 1381 !Objects.equals(targetParameter1.getClass(), targetParameter2.getClass()), 1382 "Parameter arguments must specify different parameter types"); 1383 float amplitude = extractTargetAmplitude(targetParameter1, targetParameter2); 1384 float frequencyHz = extractTargetFrequency(targetParameter1, targetParameter2); 1385 addTransitionSegment(duration, amplitude, frequencyHz); 1386 return this; 1387 } 1388 1389 /** 1390 * Add a duration to sustain the last vibration parameters of this waveform. 1391 * 1392 * <p>The duration represents how long the vibrator should sustain the last set of 1393 * parameters provided to this builder. 1394 * 1395 * @param duration The length of time the last values should be sustained by the vibrator. 1396 * Value must be >= 1ms. 1397 * @return This {@link WaveformBuilder} object to enable adding multiple transitions in 1398 * chain. 1399 * @hide 1400 */ 1401 @TestApi 1402 @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created. 1403 @NonNull addSustain(@onNull Duration duration)1404 public WaveformBuilder addSustain(@NonNull Duration duration) { 1405 int durationMs = (int) duration.toMillis(); 1406 Preconditions.checkArgument(durationMs >= 1, "Sustain duration must be >= 1ms"); 1407 mSegments.add(new StepSegment(mLastAmplitude, mLastFrequencyHz, durationMs)); 1408 return this; 1409 } 1410 1411 /** 1412 * Build the waveform as a single {@link VibrationEffect}. 1413 * 1414 * <p>The {@link WaveformBuilder} object is still valid after this call, so you can 1415 * continue adding more primitives to it and generating more {@link VibrationEffect}s by 1416 * calling this method again. 1417 * 1418 * @return The {@link VibrationEffect} resulting from the list of transitions. 1419 * @hide 1420 */ 1421 @TestApi 1422 @NonNull build()1423 public VibrationEffect build() { 1424 if (mSegments.isEmpty()) { 1425 throw new IllegalStateException( 1426 "WaveformBuilder must have at least one transition to build."); 1427 } 1428 VibrationEffect effect = new Composed(mSegments, /* repeatIndex= */ -1); 1429 effect.validate(); 1430 return effect; 1431 } 1432 checkVibrationParameter(@onNull VibrationParameter vibrationParameter, String paramName)1433 private void checkVibrationParameter(@NonNull VibrationParameter vibrationParameter, 1434 String paramName) { 1435 Preconditions.checkNotNull(vibrationParameter, "%s is null", paramName); 1436 Preconditions.checkArgument( 1437 (vibrationParameter instanceof AmplitudeVibrationParameter) 1438 || (vibrationParameter instanceof FrequencyVibrationParameter), 1439 "%s is a unknown parameter", paramName); 1440 } 1441 extractTargetAmplitude(@ullable VibrationParameter target1, @Nullable VibrationParameter target2)1442 private float extractTargetAmplitude(@Nullable VibrationParameter target1, 1443 @Nullable VibrationParameter target2) { 1444 if (target2 instanceof AmplitudeVibrationParameter) { 1445 return ((AmplitudeVibrationParameter) target2).amplitude; 1446 } 1447 if (target1 instanceof AmplitudeVibrationParameter) { 1448 return ((AmplitudeVibrationParameter) target1).amplitude; 1449 } 1450 return mLastAmplitude; 1451 } 1452 extractTargetFrequency(@ullable VibrationParameter target1, @Nullable VibrationParameter target2)1453 private float extractTargetFrequency(@Nullable VibrationParameter target1, 1454 @Nullable VibrationParameter target2) { 1455 if (target2 instanceof FrequencyVibrationParameter) { 1456 return ((FrequencyVibrationParameter) target2).frequencyHz; 1457 } 1458 if (target1 instanceof FrequencyVibrationParameter) { 1459 return ((FrequencyVibrationParameter) target1).frequencyHz; 1460 } 1461 return mLastFrequencyHz; 1462 } 1463 addTransitionSegment(Duration duration, float targetAmplitude, float targetFrequency)1464 private void addTransitionSegment(Duration duration, float targetAmplitude, 1465 float targetFrequency) { 1466 Preconditions.checkNotNull(duration, "Duration is null"); 1467 Preconditions.checkArgument(!duration.isNegative(), 1468 "Transition duration must be non-negative"); 1469 int durationMs = (int) duration.toMillis(); 1470 1471 // Ignore transitions with zero duration, but keep values for next additions. 1472 if (durationMs > 0) { 1473 if ((Math.abs(mLastAmplitude - targetAmplitude) < EPSILON) 1474 && (Math.abs(mLastFrequencyHz - targetFrequency) < EPSILON)) { 1475 // No value is changing, this can be best represented by a step segment. 1476 mSegments.add(new StepSegment(targetAmplitude, targetFrequency, durationMs)); 1477 } else { 1478 mSegments.add(new RampSegment(mLastAmplitude, targetAmplitude, 1479 mLastFrequencyHz, targetFrequency, durationMs)); 1480 } 1481 } 1482 1483 mLastAmplitude = targetAmplitude; 1484 mLastFrequencyHz = targetFrequency; 1485 } 1486 } 1487 1488 /** 1489 * A representation of a single vibration parameter. 1490 * 1491 * <p>This is to describe a waveform haptic effect, which consists of one or more timed 1492 * transitions to a new set of {@link VibrationParameter}s. 1493 * 1494 * <p>Examples of concrete parameters are the vibration amplitude or frequency. 1495 * 1496 * @see VibrationEffect.WaveformBuilder 1497 * @hide 1498 */ 1499 @TestApi 1500 @SuppressWarnings("UserHandleName") // This is not a regular set of parameters, no *Params. 1501 public static class VibrationParameter { VibrationParameter()1502 VibrationParameter() { 1503 } 1504 1505 /** 1506 * The target vibration amplitude. 1507 * 1508 * @param amplitude The amplitude value, between 0 and 1, inclusive, where 0 represents the 1509 * vibrator turned off and 1 represents the maximum amplitude the vibrator 1510 * can reach across all supported frequencies. 1511 * @return The {@link VibrationParameter} instance that represents given amplitude. 1512 * @hide 1513 */ 1514 @TestApi 1515 @NonNull targetAmplitude( @loatRangefrom = 0, to = 1) float amplitude)1516 public static VibrationParameter targetAmplitude( 1517 @FloatRange(from = 0, to = 1) float amplitude) { 1518 return new AmplitudeVibrationParameter(amplitude); 1519 } 1520 1521 /** 1522 * The target vibration frequency. 1523 * 1524 * @param frequencyHz The frequency value, in hertz. 1525 * @return The {@link VibrationParameter} instance that represents given frequency. 1526 * @hide 1527 */ 1528 @TestApi 1529 @NonNull targetFrequency(@loatRangefrom = 1) float frequencyHz)1530 public static VibrationParameter targetFrequency(@FloatRange(from = 1) float frequencyHz) { 1531 return new FrequencyVibrationParameter(frequencyHz); 1532 } 1533 } 1534 1535 /** The vibration amplitude, represented by a value in [0,1]. */ 1536 private static final class AmplitudeVibrationParameter extends VibrationParameter { 1537 public final float amplitude; 1538 AmplitudeVibrationParameter(float amplitude)1539 AmplitudeVibrationParameter(float amplitude) { 1540 Preconditions.checkArgument((amplitude >= 0) && (amplitude <= 1), 1541 "Amplitude must be within [0,1]"); 1542 this.amplitude = amplitude; 1543 } 1544 } 1545 1546 /** The vibration frequency, in hertz, or zero to represent undefined frequency. */ 1547 private static final class FrequencyVibrationParameter extends VibrationParameter { 1548 public final float frequencyHz; 1549 FrequencyVibrationParameter(float frequencyHz)1550 FrequencyVibrationParameter(float frequencyHz) { 1551 Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1"); 1552 Preconditions.checkArgument(Float.isFinite(frequencyHz), "Frequency must be finite"); 1553 this.frequencyHz = frequencyHz; 1554 } 1555 } 1556 1557 @NonNull 1558 public static final Parcelable.Creator<VibrationEffect> CREATOR = 1559 new Parcelable.Creator<VibrationEffect>() { 1560 @Override 1561 public VibrationEffect createFromParcel(Parcel in) { 1562 return new Composed(in); 1563 } 1564 @Override 1565 public VibrationEffect[] newArray(int size) { 1566 return new VibrationEffect[size]; 1567 } 1568 }; 1569 } 1570