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