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.SuppressLint; 25 import android.annotation.TestApi; 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.content.ContentResolver; 28 import android.content.Context; 29 import android.hardware.vibrator.V1_0.EffectStrength; 30 import android.hardware.vibrator.V1_3.Effect; 31 import android.net.Uri; 32 import android.os.vibrator.PrebakedSegment; 33 import android.os.vibrator.PrimitiveSegment; 34 import android.os.vibrator.RampSegment; 35 import android.os.vibrator.StepSegment; 36 import android.os.vibrator.VibrationEffectSegment; 37 import android.util.MathUtils; 38 39 import com.android.internal.util.Preconditions; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.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 * 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 57 58 /** 59 * The default vibration strength of the device. 60 */ 61 public static final int DEFAULT_AMPLITUDE = -1; 62 63 /** 64 * The maximum amplitude value 65 * @hide 66 */ 67 public static final int MAX_AMPLITUDE = 255; 68 69 /** 70 * A click effect. Use this effect as a baseline, as it's the most common type of click effect. 71 */ 72 public static final int EFFECT_CLICK = Effect.CLICK; 73 74 /** 75 * A double click effect. 76 */ 77 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK; 78 79 /** 80 * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}. 81 */ 82 public static final int EFFECT_TICK = Effect.TICK; 83 84 /** 85 * A thud effect. 86 * @see #get(int) 87 * @hide 88 */ 89 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 90 @TestApi 91 public static final int EFFECT_THUD = Effect.THUD; 92 93 /** 94 * A pop effect. 95 * @see #get(int) 96 * @hide 97 */ 98 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 99 @TestApi 100 public static final int EFFECT_POP = Effect.POP; 101 102 /** 103 * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}. 104 */ 105 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK; 106 107 /** 108 * A texture effect meant to replicate soft ticks. 109 * 110 * Unlike normal effects, texture effects are meant to be called repeatedly, generally in 111 * response to some motion, in order to replicate the feeling of some texture underneath the 112 * user's fingers. 113 * 114 * @see #get(int) 115 * @hide 116 */ 117 @TestApi 118 public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK; 119 120 /** {@hide} */ 121 @TestApi 122 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT; 123 124 /** {@hide} */ 125 @TestApi 126 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM; 127 128 /** {@hide} */ 129 @TestApi 130 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG; 131 132 /** 133 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a 134 * pattern that can be played as a ringtone with any audio, depending on the device. 135 * 136 * @see #get(Uri, Context) 137 * @hide 138 */ 139 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 140 @TestApi 141 public static final int[] RINGTONES = { 142 Effect.RINGTONE_1, 143 Effect.RINGTONE_2, 144 Effect.RINGTONE_3, 145 Effect.RINGTONE_4, 146 Effect.RINGTONE_5, 147 Effect.RINGTONE_6, 148 Effect.RINGTONE_7, 149 Effect.RINGTONE_8, 150 Effect.RINGTONE_9, 151 Effect.RINGTONE_10, 152 Effect.RINGTONE_11, 153 Effect.RINGTONE_12, 154 Effect.RINGTONE_13, 155 Effect.RINGTONE_14, 156 Effect.RINGTONE_15 157 }; 158 159 /** @hide */ 160 @IntDef(prefix = { "EFFECT_" }, value = { 161 EFFECT_TICK, 162 EFFECT_CLICK, 163 EFFECT_HEAVY_CLICK, 164 EFFECT_DOUBLE_CLICK, 165 }) 166 @Retention(RetentionPolicy.SOURCE) 167 public @interface EffectType {} 168 169 /** @hide to prevent subclassing from outside of the framework */ VibrationEffect()170 public VibrationEffect() { } 171 172 /** 173 * Create a one shot vibration. 174 * 175 * One shot vibrations will vibrate constantly for the specified period of time at the 176 * specified amplitude, and then stop. 177 * 178 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number. 179 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or 180 * {@link #DEFAULT_AMPLITUDE}. 181 * 182 * @return The desired effect. 183 */ createOneShot(long milliseconds, int amplitude)184 public static VibrationEffect createOneShot(long milliseconds, int amplitude) { 185 if (amplitude == 0) { 186 throw new IllegalArgumentException( 187 "amplitude must either be DEFAULT_AMPLITUDE, " 188 + "or between 1 and 255 inclusive (amplitude=" + amplitude + ")"); 189 } 190 return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */); 191 } 192 193 /** 194 * Create a waveform vibration. 195 * 196 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For 197 * each pair, the value in the amplitude array determines the strength of the vibration and the 198 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no 199 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored. 200 * <p> 201 * The amplitude array of the generated waveform will be the same size as the given 202 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE}, 203 * starting with 0. Therefore the first timing value will be the period to wait before turning 204 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE} 205 * strength, etc. 206 * </p><p> 207 * To cause the pattern to repeat, pass the index into the timings array at which to start the 208 * repetition, or -1 to disable repeating. 209 * </p> 210 * 211 * @param timings The pattern of alternating on-off timings, starting with off. Timing values 212 * of 0 will cause the timing / amplitude pair to be ignored. 213 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't 214 * want to repeat. 215 * 216 * @return The desired effect. 217 */ createWaveform(long[] timings, int repeat)218 public static VibrationEffect createWaveform(long[] timings, int repeat) { 219 int[] amplitudes = new int[timings.length]; 220 for (int i = 0; i < (timings.length / 2); i++) { 221 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE; 222 } 223 return createWaveform(timings, amplitudes, repeat); 224 } 225 226 /** 227 * Create a waveform vibration. 228 * 229 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For 230 * each pair, the value in the amplitude array determines the strength of the vibration and the 231 * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude 232 * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any 233 * pairs with a timing value of 0 will be ignored. 234 * </p><p> 235 * To cause the pattern to repeat, pass the index into the timings array at which to start the 236 * repetition, or -1 to disable repeating. 237 * </p> 238 * 239 * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing 240 * values of 0 will cause the pair to be ignored. 241 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values 242 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An 243 * amplitude value of 0 implies the motor is off. 244 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't 245 * want to repeat. 246 * 247 * @return The desired effect. 248 */ createWaveform(long[] timings, int[] amplitudes, int repeat)249 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { 250 if (timings.length != amplitudes.length) { 251 throw new IllegalArgumentException( 252 "timing and amplitude arrays must be of equal length" 253 + " (timings.length=" + timings.length 254 + ", amplitudes.length=" + amplitudes.length + ")"); 255 } 256 List<StepSegment> segments = new ArrayList<>(); 257 for (int i = 0; i < timings.length; i++) { 258 float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE 259 ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE; 260 segments.add(new StepSegment(parsedAmplitude, /* frequency= */ 0, (int) timings[i])); 261 } 262 VibrationEffect effect = new Composed(segments, repeat); 263 effect.validate(); 264 return effect; 265 } 266 267 /** 268 * Create a predefined vibration effect. 269 * 270 * Predefined effects are a set of common vibration effects that should be identical, regardless 271 * of the app they come from, in order to provide a cohesive experience for users across 272 * the entire device. They also may be custom tailored to the device hardware in order to 273 * provide a better experience than you could otherwise build using the generic building 274 * blocks. 275 * 276 * This will fallback to a generic pattern if one exists and there does not exist a 277 * hardware-specific implementation of the effect. 278 * 279 * @param effectId The ID of the effect to perform: 280 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 281 * 282 * @return The desired effect. 283 */ 284 @NonNull createPredefined(@ffectType int effectId)285 public static VibrationEffect createPredefined(@EffectType int effectId) { 286 return get(effectId, true); 287 } 288 289 /** 290 * Get a predefined vibration effect. 291 * 292 * Predefined effects are a set of common vibration effects that should be identical, regardless 293 * of the app they come from, in order to provide a cohesive experience for users across 294 * the entire device. They also may be custom tailored to the device hardware in order to 295 * provide a better experience than you could otherwise build using the generic building 296 * blocks. 297 * 298 * This will fallback to a generic pattern if one exists and there does not exist a 299 * hardware-specific implementation of the effect. 300 * 301 * @param effectId The ID of the effect to perform: 302 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 303 * 304 * @return The desired effect. 305 * @hide 306 */ 307 @TestApi get(int effectId)308 public static VibrationEffect get(int effectId) { 309 return get(effectId, true); 310 } 311 312 /** 313 * Get a predefined vibration effect. 314 * 315 * Predefined effects are a set of common vibration effects that should be identical, regardless 316 * of the app they come from, in order to provide a cohesive experience for users across 317 * the entire device. They also may be custom tailored to the device hardware in order to 318 * provide a better experience than you could otherwise build using the generic building 319 * blocks. 320 * 321 * Some effects you may only want to play if there's a hardware specific implementation because 322 * they may, for example, be too disruptive to the user without tuning. The {@code fallback} 323 * parameter allows you to decide whether you want to fallback to the generic implementation or 324 * only play if there's a tuned, hardware specific one available. 325 * 326 * @param effectId The ID of the effect to perform: 327 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 328 * @param fallback Whether to fallback to a generic pattern if a hardware specific 329 * implementation doesn't exist. 330 * 331 * @return The desired effect. 332 * @hide 333 */ 334 @TestApi get(int effectId, boolean fallback)335 public static VibrationEffect get(int effectId, boolean fallback) { 336 VibrationEffect effect = new Composed( 337 new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM)); 338 effect.validate(); 339 return effect; 340 } 341 342 /** 343 * Get a predefined vibration effect associated with a given URI. 344 * 345 * Predefined effects are a set of common vibration effects that should be identical, regardless 346 * of the app they come from, in order to provide a cohesive experience for users across 347 * the entire device. They also may be custom tailored to the device hardware in order to 348 * provide a better experience than you could otherwise build using the generic building 349 * blocks. 350 * 351 * @param uri The URI associated with the haptic effect. 352 * @param context The context used to get the URI to haptic effect association. 353 * 354 * @return The desired effect, or {@code null} if there's no associated effect. 355 * 356 * @hide 357 */ 358 @TestApi 359 @Nullable get(Uri uri, Context context)360 public static VibrationEffect get(Uri uri, Context context) { 361 String[] uris = context.getResources().getStringArray( 362 com.android.internal.R.array.config_ringtoneEffectUris); 363 364 // Skip doing any IPC if we don't have any effects configured. 365 if (uris.length == 0) { 366 return null; 367 } 368 369 final ContentResolver cr = context.getContentResolver(); 370 Uri uncanonicalUri = cr.uncanonicalize(uri); 371 if (uncanonicalUri == null) { 372 // If we already had an uncanonical URI, it's possible we'll get null back here. In 373 // this case, just use the URI as passed in since it wasn't canonicalized in the first 374 // place. 375 uncanonicalUri = uri; 376 } 377 378 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) { 379 if (uris[i] == null) { 380 continue; 381 } 382 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i])); 383 if (mappedUri == null) { 384 continue; 385 } 386 if (mappedUri.equals(uncanonicalUri)) { 387 return get(RINGTONES[i]); 388 } 389 } 390 return null; 391 } 392 393 /** 394 * Start composing a haptic effect. 395 * 396 * @see VibrationEffect.Composition 397 */ 398 @NonNull startComposition()399 public static Composition startComposition() { 400 return new Composition(); 401 } 402 403 /** 404 * Start building a waveform vibration. 405 * 406 * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing 407 * control over vibration frequency and ramping up or down the vibration amplitude, frequency or 408 * both. 409 * 410 * <p>For simpler waveform patterns see {@link #createWaveform} methods. 411 * 412 * @hide 413 * @see VibrationEffect.WaveformBuilder 414 */ 415 @TestApi 416 @NonNull startWaveform()417 public static WaveformBuilder startWaveform() { 418 return new WaveformBuilder(); 419 } 420 421 @Override describeContents()422 public int describeContents() { 423 return 0; 424 } 425 426 /** @hide */ validate()427 public abstract void validate(); 428 429 /** 430 * Gets the estimated duration of the vibration in milliseconds. 431 * 432 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this 433 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where 434 * the length is device and potentially run-time dependent), this returns -1. 435 * 436 * @hide 437 */ 438 @TestApi getDuration()439 public abstract long getDuration(); 440 441 /** 442 * Resolve default values into integer amplitude numbers. 443 * 444 * @param defaultAmplitude the default amplitude to apply, must be between 0 and 445 * MAX_AMPLITUDE 446 * @return this if amplitude value is already set, or a copy of this effect with given default 447 * amplitude otherwise 448 * 449 * @hide 450 */ resolve(int defaultAmplitude)451 public abstract <T extends VibrationEffect> T resolve(int defaultAmplitude); 452 453 /** 454 * Scale the vibration effect intensity with the given constraints. 455 * 456 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will 457 * scale down the intensity, values larger than 1 will scale up 458 * @return this if there is no scaling to be done, or a copy of this effect with scaled 459 * vibration intensity otherwise 460 * 461 * @hide 462 */ scale(float scaleFactor)463 public abstract <T extends VibrationEffect> T scale(float scaleFactor); 464 465 /** 466 * Applies given effect strength to prebaked effects represented by one of 467 * VibrationEffect.EFFECT_*. 468 * 469 * @param effectStrength new effect strength to be applied, one of 470 * VibrationEffect.EFFECT_STRENGTH_*. 471 * @return this if there is no change to this effect, or a copy of this effect with applied 472 * effect strength otherwise. 473 * @hide 474 */ applyEffectStrength(int effectStrength)475 public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) { 476 return (T) this; 477 } 478 479 /** 480 * Scale given vibration intensity by the given factor. 481 * 482 * @param intensity relative intensity of the effect, must be between 0 and 1 483 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will 484 * scale down the intensity, values larger than 1 will scale up 485 * @hide 486 */ scale(float intensity, float scaleFactor)487 public static float scale(float intensity, float scaleFactor) { 488 // Applying gamma correction to the scale factor, which is the same as encoding the input 489 // value, scaling it, then decoding the scaled value. 490 float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA); 491 492 if (scaleFactor <= 1) { 493 // Scale down is simply a gamma corrected application of scaleFactor to the intensity. 494 // Scale up requires a different curve to ensure the intensity will not become > 1. 495 return intensity * scale; 496 } 497 498 // Apply the scale factor a few more times to make the ramp curve closer to the raw scale. 499 float extraScale = MathUtils.pow(scaleFactor, 4f - scaleFactor); 500 float x = intensity * scale * extraScale; 501 float maxX = scale * extraScale; // scaled x for intensity == 1 502 503 float expX = MathUtils.exp(x); 504 float expMaxX = MathUtils.exp(maxX); 505 506 // Using f = tanh as the scale up function so the max value will converge. 507 // a = 1/f(maxX), used to scale f so that a*f(maxX) = 1 (the value will converge to 1). 508 float a = (expMaxX + 1f) / (expMaxX - 1f); 509 float fx = (expX - 1f) / (expX + 1f); 510 511 return MathUtils.constrain(a * fx, 0f, 1f); 512 } 513 514 /** @hide */ effectIdToString(int effectId)515 public static String effectIdToString(int effectId) { 516 switch (effectId) { 517 case EFFECT_CLICK: 518 return "CLICK"; 519 case EFFECT_TICK: 520 return "TICK"; 521 case EFFECT_HEAVY_CLICK: 522 return "HEAVY_CLICK"; 523 case EFFECT_DOUBLE_CLICK: 524 return "DOUBLE_CLICK"; 525 case EFFECT_POP: 526 return "POP"; 527 case EFFECT_THUD: 528 return "THUD"; 529 case EFFECT_TEXTURE_TICK: 530 return "TEXTURE_TICK"; 531 default: 532 return Integer.toString(effectId); 533 } 534 } 535 536 /** @hide */ effectStrengthToString(int effectStrength)537 public static String effectStrengthToString(int effectStrength) { 538 switch (effectStrength) { 539 case EFFECT_STRENGTH_LIGHT: 540 return "LIGHT"; 541 case EFFECT_STRENGTH_MEDIUM: 542 return "MEDIUM"; 543 case EFFECT_STRENGTH_STRONG: 544 return "STRONG"; 545 default: 546 return Integer.toString(effectStrength); 547 } 548 } 549 550 /** 551 * Implementation of {@link VibrationEffect} described by a composition of one or more 552 * {@link VibrationEffectSegment}, with an optional index to represent repeating effects. 553 * 554 * @hide 555 */ 556 @TestApi 557 public static final class Composed extends VibrationEffect { 558 private final ArrayList<VibrationEffectSegment> mSegments; 559 private final int mRepeatIndex; 560 Composed(@onNull Parcel in)561 Composed(@NonNull Parcel in) { 562 this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt()); 563 } 564 Composed(@onNull VibrationEffectSegment segment)565 Composed(@NonNull VibrationEffectSegment segment) { 566 this(Arrays.asList(segment), /* repeatIndex= */ -1); 567 } 568 569 /** @hide */ Composed(@onNull List<? extends VibrationEffectSegment> segments, int repeatIndex)570 public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) { 571 super(); 572 mSegments = new ArrayList<>(segments); 573 mRepeatIndex = repeatIndex; 574 } 575 576 @NonNull getSegments()577 public List<VibrationEffectSegment> getSegments() { 578 return mSegments; 579 } 580 getRepeatIndex()581 public int getRepeatIndex() { 582 return mRepeatIndex; 583 } 584 585 @Override validate()586 public void validate() { 587 int segmentCount = mSegments.size(); 588 boolean hasNonZeroDuration = false; 589 for (int i = 0; i < segmentCount; i++) { 590 VibrationEffectSegment segment = mSegments.get(i); 591 segment.validate(); 592 // A segment with unknown duration = -1 still counts as a non-zero duration. 593 hasNonZeroDuration |= segment.getDuration() != 0; 594 } 595 if (!hasNonZeroDuration) { 596 throw new IllegalArgumentException("at least one timing must be non-zero" 597 + " (segments=" + mSegments + ")"); 598 } 599 if (mRepeatIndex != -1) { 600 Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1, 601 "repeat index must be within the bounds of the segments (segments.length=" 602 + segmentCount + ", index=" + mRepeatIndex + ")"); 603 } 604 } 605 606 @Override getDuration()607 public long getDuration() { 608 if (mRepeatIndex >= 0) { 609 return Long.MAX_VALUE; 610 } 611 int segmentCount = mSegments.size(); 612 long totalDuration = 0; 613 for (int i = 0; i < segmentCount; i++) { 614 long segmentDuration = mSegments.get(i).getDuration(); 615 if (segmentDuration < 0) { 616 return segmentDuration; 617 } 618 totalDuration += segmentDuration; 619 } 620 return totalDuration; 621 } 622 623 @NonNull 624 @Override resolve(int defaultAmplitude)625 public Composed resolve(int defaultAmplitude) { 626 int segmentCount = mSegments.size(); 627 ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount); 628 for (int i = 0; i < segmentCount; i++) { 629 resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude)); 630 } 631 if (resolvedSegments.equals(mSegments)) { 632 return this; 633 } 634 Composed resolved = new Composed(resolvedSegments, mRepeatIndex); 635 resolved.validate(); 636 return resolved; 637 } 638 639 @NonNull 640 @Override scale(float scaleFactor)641 public Composed scale(float scaleFactor) { 642 int segmentCount = mSegments.size(); 643 ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount); 644 for (int i = 0; i < segmentCount; i++) { 645 scaledSegments.add(mSegments.get(i).scale(scaleFactor)); 646 } 647 if (scaledSegments.equals(mSegments)) { 648 return this; 649 } 650 Composed scaled = new Composed(scaledSegments, mRepeatIndex); 651 scaled.validate(); 652 return scaled; 653 } 654 655 @NonNull 656 @Override applyEffectStrength(int effectStrength)657 public Composed applyEffectStrength(int effectStrength) { 658 int segmentCount = mSegments.size(); 659 ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount); 660 for (int i = 0; i < segmentCount; i++) { 661 scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength)); 662 } 663 if (scaledSegments.equals(mSegments)) { 664 return this; 665 } 666 Composed scaled = new Composed(scaledSegments, mRepeatIndex); 667 scaled.validate(); 668 return scaled; 669 } 670 671 @Override equals(@ullable Object o)672 public boolean equals(@Nullable Object o) { 673 if (!(o instanceof Composed)) { 674 return false; 675 } 676 Composed other = (Composed) o; 677 return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex; 678 } 679 680 @Override hashCode()681 public int hashCode() { 682 return Objects.hash(mSegments, mRepeatIndex); 683 } 684 685 @Override toString()686 public String toString() { 687 return "Composed{segments=" + mSegments 688 + ", repeat=" + mRepeatIndex 689 + "}"; 690 } 691 692 @Override describeContents()693 public int describeContents() { 694 return 0; 695 } 696 697 @Override writeToParcel(@onNull Parcel out, int flags)698 public void writeToParcel(@NonNull Parcel out, int flags) { 699 out.writeList(mSegments); 700 out.writeInt(mRepeatIndex); 701 } 702 703 @NonNull 704 public static final Creator<Composed> CREATOR = 705 new Creator<Composed>() { 706 @Override 707 public Composed createFromParcel(Parcel in) { 708 return new Composed(in); 709 } 710 711 @Override 712 public Composed[] newArray(int size) { 713 return new Composed[size]; 714 } 715 }; 716 } 717 718 /** 719 * A composition of haptic primitives that, when combined, create a single haptic effect. 720 * 721 * @see VibrationEffect#startComposition() 722 */ 723 public static final class Composition { 724 /** @hide */ 725 @IntDef(prefix = { "PRIMITIVE_" }, value = { 726 PRIMITIVE_CLICK, 727 PRIMITIVE_THUD, 728 PRIMITIVE_SPIN, 729 PRIMITIVE_QUICK_RISE, 730 PRIMITIVE_SLOW_RISE, 731 PRIMITIVE_QUICK_FALL, 732 PRIMITIVE_TICK, 733 PRIMITIVE_LOW_TICK, 734 }) 735 @Retention(RetentionPolicy.SOURCE) 736 public @interface PrimitiveType {} 737 738 /** 739 * No haptic effect. Used to generate extended delays between primitives. 740 * @hide 741 */ 742 public static final int PRIMITIVE_NOOP = 0; 743 /** 744 * This effect should produce a sharp, crisp click sensation. 745 */ 746 public static final int PRIMITIVE_CLICK = 1; 747 /** 748 * A haptic effect that simulates downwards movement with gravity. Often 749 * followed by extra energy of hitting and reverberation to augment 750 * physicality. 751 */ 752 public static final int PRIMITIVE_THUD = 2; 753 /** 754 * A haptic effect that simulates spinning momentum. 755 */ 756 public static final int PRIMITIVE_SPIN = 3; 757 /** 758 * A haptic effect that simulates quick upward movement against gravity. 759 */ 760 public static final int PRIMITIVE_QUICK_RISE = 4; 761 /** 762 * A haptic effect that simulates slow upward movement against gravity. 763 */ 764 public static final int PRIMITIVE_SLOW_RISE = 5; 765 /** 766 * A haptic effect that simulates quick downwards movement with gravity. 767 */ 768 public static final int PRIMITIVE_QUICK_FALL = 6; 769 /** 770 * This very short effect should produce a light crisp sensation intended 771 * to be used repetitively for dynamic feedback. 772 */ 773 // Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK 774 public static final int PRIMITIVE_TICK = 7; 775 /** 776 * This very short low frequency effect should produce a light crisp sensation 777 * intended to be used repetitively for dynamic feedback. 778 */ 779 // Internally this maps to the HAL constant CompositePrimitive::LOW_TICK 780 public static final int PRIMITIVE_LOW_TICK = 8; 781 782 783 private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); 784 private int mRepeatIndex = -1; 785 Composition()786 Composition() {} 787 788 /** 789 * Add a haptic effect to the end of the current composition. 790 * 791 * <p>Similar to {@link #addEffect(VibrationEffect, int)} , but with no delay applied. 792 * 793 * @param effect The effect to add to this composition as a primitive 794 * @return The {@link Composition} object to enable adding multiple primitives in one chain. 795 * @hide 796 */ 797 @TestApi 798 @NonNull addEffect(@onNull VibrationEffect effect)799 public Composition addEffect(@NonNull VibrationEffect effect) { 800 return addEffect(effect, /* delay= */ 0); 801 } 802 803 /** 804 * Add a haptic effect to the end of the current composition. 805 * 806 * @param effect The effect to add to this composition as a primitive 807 * @param delay The amount of time in milliseconds to wait before playing this primitive 808 * @return The {@link Composition} object to enable adding multiple primitives in one chain. 809 * @hide 810 */ 811 @TestApi 812 @NonNull addEffect(@onNull VibrationEffect effect, @IntRange(from = 0) int delay)813 public Composition addEffect(@NonNull VibrationEffect effect, 814 @IntRange(from = 0) int delay) { 815 Preconditions.checkArgumentNonnegative(delay); 816 if (delay > 0) { 817 // Created a segment sustaining the zero amplitude to represent the delay. 818 addSegment(new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, 819 /* duration= */ delay)); 820 } 821 return addSegments(effect); 822 } 823 824 /** 825 * Add a haptic primitive to the end of the current composition. 826 * 827 * Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a 828 * default scale applied. 829 * 830 * @param primitiveId The primitive to add 831 * 832 * @return The {@link Composition} object to enable adding multiple primitives in one chain. 833 */ 834 @NonNull addPrimitive(@rimitiveType int primitiveId)835 public Composition addPrimitive(@PrimitiveType int primitiveId) { 836 return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); 837 } 838 839 /** 840 * Add a haptic primitive to the end of the current composition. 841 * 842 * Similar to {@link #addPrimitive(int, float, int)}, but with no delay. 843 * 844 * @param primitiveId The primitive to add 845 * @param scale The scale to apply to the intensity of the primitive. 846 * 847 * @return The {@link Composition} object to enable adding multiple primitives in one chain. 848 */ 849 @NonNull addPrimitive(@rimitiveType int primitiveId, @FloatRange(from = 0f, to = 1f) float scale)850 public Composition addPrimitive(@PrimitiveType int primitiveId, 851 @FloatRange(from = 0f, to = 1f) float scale) { 852 return addPrimitive(primitiveId, scale, /*delay*/ 0); 853 } 854 855 /** 856 * Add a haptic primitive to the end of the current composition. 857 * 858 * @param primitiveId The primitive to add 859 * @param scale The scale to apply to the intensity of the primitive. 860 * @param delay The amount of time in milliseconds to wait before playing this primitive, 861 * starting at the time the previous element in this composition is finished. 862 * @return The {@link Composition} object to enable adding multiple primitives in one chain. 863 */ 864 @NonNull addPrimitive(@rimitiveType int primitiveId, @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay)865 public Composition addPrimitive(@PrimitiveType int primitiveId, 866 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) { 867 PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, 868 delay); 869 primitive.validate(); 870 return addSegment(primitive); 871 } 872 addSegment(VibrationEffectSegment segment)873 private Composition addSegment(VibrationEffectSegment segment) { 874 if (mRepeatIndex >= 0) { 875 throw new IllegalStateException( 876 "Composition already have a repeating effect so any new primitive would be" 877 + " unreachable."); 878 } 879 mSegments.add(segment); 880 return this; 881 } 882 addSegments(VibrationEffect effect)883 private Composition addSegments(VibrationEffect effect) { 884 if (mRepeatIndex >= 0) { 885 throw new IllegalStateException( 886 "Composition already have a repeating effect so any new primitive would be" 887 + " unreachable."); 888 } 889 Composed composed = (Composed) effect; 890 if (composed.getRepeatIndex() >= 0) { 891 // Start repeating from the index relative to the composed waveform. 892 mRepeatIndex = mSegments.size() + composed.getRepeatIndex(); 893 } 894 mSegments.addAll(composed.getSegments()); 895 return this; 896 } 897 898 /** 899 * Compose all of the added primitives together into a single {@link VibrationEffect}. 900 * 901 * The {@link Composition} object is still valid after this call, so you can continue adding 902 * more primitives to it and generating more {@link VibrationEffect}s by calling this method 903 * again. 904 * 905 * @return The {@link VibrationEffect} resulting from the composition of the primitives. 906 */ 907 @NonNull compose()908 public VibrationEffect compose() { 909 if (mSegments.isEmpty()) { 910 throw new IllegalStateException( 911 "Composition must have at least one element to compose."); 912 } 913 VibrationEffect effect = new Composed(mSegments, mRepeatIndex); 914 effect.validate(); 915 return effect; 916 } 917 918 /** 919 * Convert the primitive ID to a human readable string for debugging 920 * @param id The ID to convert 921 * @return The ID in a human readable format. 922 * @hide 923 */ primitiveToString(@rimitiveType int id)924 public static String primitiveToString(@PrimitiveType int id) { 925 switch (id) { 926 case PRIMITIVE_NOOP: 927 return "PRIMITIVE_NOOP"; 928 case PRIMITIVE_CLICK: 929 return "PRIMITIVE_CLICK"; 930 case PRIMITIVE_THUD: 931 return "PRIMITIVE_THUD"; 932 case PRIMITIVE_SPIN: 933 return "PRIMITIVE_SPIN"; 934 case PRIMITIVE_QUICK_RISE: 935 return "PRIMITIVE_QUICK_RISE"; 936 case PRIMITIVE_SLOW_RISE: 937 return "PRIMITIVE_SLOW_RISE"; 938 case PRIMITIVE_QUICK_FALL: 939 return "PRIMITIVE_QUICK_FALL"; 940 case PRIMITIVE_TICK: 941 return "PRIMITIVE_TICK"; 942 case PRIMITIVE_LOW_TICK: 943 return "PRIMITIVE_LOW_TICK"; 944 default: 945 return Integer.toString(id); 946 } 947 } 948 } 949 950 /** 951 * A builder for waveform haptic effects. 952 * 953 * <p>Waveform vibrations constitute of one or more timed segments where the vibration 954 * amplitude, frequency or both can linearly ramp to new values. 955 * 956 * <p>Waveform segments may have zero duration, which represent a jump to new vibration 957 * amplitude and/or frequency values. 958 * 959 * <p>Waveform segments may have the same start and end vibration amplitude and frequency, 960 * which represent a step where the amplitude and frequency are maintained for that duration. 961 * 962 * @hide 963 * @see VibrationEffect#startWaveform() 964 */ 965 @TestApi 966 public static final class WaveformBuilder { 967 private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); 968 WaveformBuilder()969 WaveformBuilder() {} 970 971 /** 972 * Vibrate with given amplitude for the given duration, in millis, keeping the previous 973 * frequency the same. 974 * 975 * <p>If the duration is zero the vibrator will jump to new amplitude. 976 * 977 * @param amplitude The amplitude for this step 978 * @param duration The duration of this step in milliseconds 979 * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. 980 */ 981 @SuppressLint("MissingGetterMatchingBuilder") 982 @NonNull addStep(@loatRangefrom = 0f, to = 1f) float amplitude, @IntRange(from = 0) int duration)983 public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, 984 @IntRange(from = 0) int duration) { 985 return addStep(amplitude, getPreviousFrequency(), duration); 986 } 987 988 /** 989 * Vibrate with given amplitude for the given duration, in millis, keeping the previous 990 * vibration frequency the same. 991 * 992 * <p>If the duration is zero the vibrator will jump to new amplitude. 993 * 994 * @param amplitude The amplitude for this step 995 * @param frequency The frequency for this step 996 * @param duration The duration of this step in milliseconds 997 * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. 998 */ 999 @SuppressLint("MissingGetterMatchingBuilder") 1000 @NonNull addStep(@loatRangefrom = 0f, to = 1f) float amplitude, @FloatRange(from = -1f, to = 1f) float frequency, @IntRange(from = 0) int duration)1001 public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, 1002 @FloatRange(from = -1f, to = 1f) float frequency, 1003 @IntRange(from = 0) int duration) { 1004 mSegments.add(new StepSegment(amplitude, frequency, duration)); 1005 return this; 1006 } 1007 1008 /** 1009 * Ramp vibration linearly for the given duration, in millis, from previous amplitude value 1010 * to the given one, keeping previous frequency. 1011 * 1012 * <p>If the duration is zero the vibrator will jump to new amplitude. 1013 * 1014 * @param amplitude The final amplitude this ramp should reach 1015 * @param duration The duration of this ramp in milliseconds 1016 * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. 1017 */ 1018 @SuppressLint("MissingGetterMatchingBuilder") 1019 @NonNull addRamp(@loatRangefrom = 0f, to = 1f) float amplitude, @IntRange(from = 0) int duration)1020 public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, 1021 @IntRange(from = 0) int duration) { 1022 return addRamp(amplitude, getPreviousFrequency(), duration); 1023 } 1024 1025 /** 1026 * Ramp vibration linearly for the given duration, in millis, from previous amplitude and 1027 * frequency values to the given ones. 1028 * 1029 * <p>If the duration is zero the vibrator will jump to new amplitude and frequency. 1030 * 1031 * @param amplitude The final amplitude this ramp should reach 1032 * @param frequency The final frequency this ramp should reach 1033 * @param duration The duration of this ramp in milliseconds 1034 * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. 1035 */ 1036 @SuppressLint("MissingGetterMatchingBuilder") 1037 @NonNull addRamp(@loatRangefrom = 0f, to = 1f) float amplitude, @FloatRange(from = -1f, to = 1f) float frequency, @IntRange(from = 0) int duration)1038 public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, 1039 @FloatRange(from = -1f, to = 1f) float frequency, 1040 @IntRange(from = 0) int duration) { 1041 mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude, getPreviousFrequency(), 1042 frequency, duration)); 1043 return this; 1044 } 1045 1046 /** 1047 * Compose all of the steps together into a single {@link VibrationEffect}. 1048 * 1049 * The {@link WaveformBuilder} object is still valid after this call, so you can 1050 * continue adding more primitives to it and generating more {@link VibrationEffect}s by 1051 * calling this method again. 1052 * 1053 * @return The {@link VibrationEffect} resulting from the composition of the steps. 1054 */ 1055 @NonNull build()1056 public VibrationEffect build() { 1057 return build(/* repeat= */ -1); 1058 } 1059 1060 /** 1061 * Compose all of the steps together into a single {@link VibrationEffect}. 1062 * 1063 * <p>To cause the pattern to repeat, pass the index at which to start the repetition 1064 * (starting at 0), or -1 to disable repeating. 1065 * 1066 * <p>The {@link WaveformBuilder} object is still valid after this call, so you can 1067 * continue adding more primitives to it and generating more {@link VibrationEffect}s by 1068 * calling this method again. 1069 * 1070 * @return The {@link VibrationEffect} resulting from the composition of the steps. 1071 */ 1072 @NonNull build(int repeat)1073 public VibrationEffect build(int repeat) { 1074 if (mSegments.isEmpty()) { 1075 throw new IllegalStateException( 1076 "WaveformBuilder must have at least one element to build."); 1077 } 1078 VibrationEffect effect = new Composed(mSegments, repeat); 1079 effect.validate(); 1080 return effect; 1081 } 1082 getPreviousFrequency()1083 private float getPreviousFrequency() { 1084 if (!mSegments.isEmpty()) { 1085 VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1); 1086 if (segment instanceof StepSegment) { 1087 return ((StepSegment) segment).getFrequency(); 1088 } else if (segment instanceof RampSegment) { 1089 return ((RampSegment) segment).getEndFrequency(); 1090 } 1091 } 1092 return 0; 1093 } 1094 getPreviousAmplitude()1095 private float getPreviousAmplitude() { 1096 if (!mSegments.isEmpty()) { 1097 VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1); 1098 if (segment instanceof StepSegment) { 1099 return ((StepSegment) segment).getAmplitude(); 1100 } else if (segment instanceof RampSegment) { 1101 return ((RampSegment) segment).getEndAmplitude(); 1102 } 1103 } 1104 return 0; 1105 } 1106 } 1107 1108 @NonNull 1109 public static final Parcelable.Creator<VibrationEffect> CREATOR = 1110 new Parcelable.Creator<VibrationEffect>() { 1111 @Override 1112 public VibrationEffect createFromParcel(Parcel in) { 1113 return new Composed(in); 1114 } 1115 @Override 1116 public VibrationEffect[] newArray(int size) { 1117 return new VibrationEffect[size]; 1118 } 1119 }; 1120 } 1121