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