1 /* 2 * Copyright (C) 2020 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.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.TestApi; 22 import android.os.vibrator.Flags; 23 import android.util.SparseArray; 24 25 import com.android.internal.util.Preconditions; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Locale; 30 import java.util.Objects; 31 import java.util.StringJoiner; 32 import java.util.function.Function; 33 34 /** 35 * A CombinedVibration describes a combination of haptic effects to be performed by one or more 36 * {@link Vibrator Vibrators}. 37 * 38 * These effects may be any number of things, from single shot vibrations to complex waveforms. 39 * 40 * @see VibrationEffect 41 */ 42 @SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here. 43 public abstract class CombinedVibration implements Parcelable { 44 private static final int PARCEL_TOKEN_MONO = 1; 45 private static final int PARCEL_TOKEN_STEREO = 2; 46 private static final int PARCEL_TOKEN_SEQUENTIAL = 3; 47 48 /** Prevent subclassing from outside of the framework. */ CombinedVibration()49 CombinedVibration() { 50 } 51 52 /** 53 * Create a vibration that plays a single effect in parallel on all vibrators. 54 * 55 * A parallel vibration that takes a single {@link VibrationEffect} to be performed by multiple 56 * vibrators at the same time. 57 * 58 * @param effect The {@link VibrationEffect} to perform. 59 * @return The combined vibration representing the single effect to be played in all vibrators. 60 */ 61 @NonNull createParallel(@onNull VibrationEffect effect)62 public static CombinedVibration createParallel(@NonNull VibrationEffect effect) { 63 CombinedVibration combined = new Mono(effect); 64 combined.validate(); 65 return combined; 66 } 67 68 /** 69 * Start creating a vibration that plays effects in parallel on one or more vibrators. 70 * 71 * A parallel vibration takes one or more {@link VibrationEffect VibrationEffects} associated to 72 * individual vibrators to be performed at the same time. 73 * 74 * @see CombinedVibration.ParallelCombination 75 */ 76 @NonNull startParallel()77 public static ParallelCombination startParallel() { 78 return new ParallelCombination(); 79 } 80 81 /** 82 * Start creating a vibration that plays effects in sequence on one or more vibrators. 83 * 84 * A sequential vibration takes one or more {@link CombinedVibration CombinedVibrations} to be 85 * performed by one or more vibrators in order. Each {@link CombinedVibration} starts only after 86 * the previous one is finished. 87 * 88 * @hide 89 * @see CombinedVibration.SequentialCombination 90 */ 91 @TestApi 92 @NonNull startSequential()93 public static SequentialCombination startSequential() { 94 return new SequentialCombination(); 95 } 96 97 @Override describeContents()98 public int describeContents() { 99 return 0; 100 } 101 102 /** 103 * Gets the estimated duration of the combined vibration in milliseconds. 104 * 105 * <p>For parallel combinations this means the maximum duration of any individual {@link 106 * VibrationEffect}. For sequential combinations, this is a sum of each step and delays. 107 * 108 * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative 109 * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g. 110 * Prebaked effects where the length is device and potentially run-time dependent), this returns 111 * -1. 112 * 113 * @hide 114 */ 115 @TestApi getDuration()116 public abstract long getDuration(); 117 118 /** 119 * Gets the estimated duration of the combined vibration in milliseconds. 120 * 121 * <p>For effects with hardware-dependent constants (e.g. primitive compositions), this returns 122 * the estimated duration based on the {@link VibratorInfo}. For all other effects this will 123 * return the same as {@link #getDuration()}. 124 * 125 * @hide 126 */ getDuration(@ullable SparseArray<VibratorInfo> vibratorInfos)127 public abstract long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos); 128 129 /** 130 * Returns true if this effect could represent a touch haptic feedback. 131 * 132 * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified 133 * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN, 134 * then this method will be used to classify the most common use case and make sure they are 135 * covered by the user settings for "Touch feedback". 136 * 137 * @hide 138 */ isHapticFeedbackCandidate()139 public boolean isHapticFeedbackCandidate() { 140 return false; 141 } 142 143 /** @hide */ validate()144 public abstract void validate(); 145 146 /** 147 * Applies given effect transformation with a fixed parameter to each effect in this vibration. 148 * 149 * @param transformation The vibration effect transformation to be applied to all effects 150 * @param param The fixed parameter to be applied in all effect transformations 151 * @return the result of running the given transformation on all effects of this vibration 152 * @hide 153 */ transform( VibrationEffect.Transformation<ParamT> transformation, ParamT param)154 public abstract <ParamT> CombinedVibration transform( 155 VibrationEffect.Transformation<ParamT> transformation, ParamT param); 156 157 /** 158 * Applies given vibrator adapter to each effect in this combined vibration. 159 * 160 * @param adapter The vibrator adapter to be used on this vibration 161 * @return the result of running the given adapter on all effects of this vibration 162 * @hide 163 */ adapt(VibratorAdapter adapter)164 public abstract CombinedVibration adapt(VibratorAdapter adapter); 165 166 /** @hide */ hasVibrator(int vibratorId)167 public abstract boolean hasVibrator(int vibratorId); 168 169 /** @hide */ hasVendorEffects()170 public abstract boolean hasVendorEffects(); 171 172 /** 173 * Returns a compact version of the {@link #toString()} result for debugging purposes. 174 * 175 * @hide 176 */ toDebugString()177 public abstract String toDebugString(); 178 179 /** 180 * Adapts a {@link VibrationEffect} to a specific device vibrator using the ID. 181 * 182 * <p>This can be used for adapting effects to the capabilities of the specific device vibrator 183 * it's been mapped to by the combined vibration. 184 * 185 * @hide 186 */ 187 public interface VibratorAdapter { 188 189 /** 190 * Return the list of vibrator IDs available on the device, to be used by {@link 191 * CombinedVibration} to fan-out individual effects that aren't assigned to a specific 192 * vibrator. 193 */ getAvailableVibratorIds()194 int[] getAvailableVibratorIds(); 195 196 /** Adapts a {@link VibrationEffect} to a given vibrator. */ adaptToVibrator(int vibratorId, @NonNull VibrationEffect effect)197 VibrationEffect adaptToVibrator(int vibratorId, @NonNull VibrationEffect effect); 198 } 199 200 /** 201 * A combination of haptic effects that should be played in multiple vibrators in parallel. 202 * 203 * @see CombinedVibration#startParallel() 204 */ 205 public static final class ParallelCombination { 206 207 private final SparseArray<VibrationEffect> mEffects = new SparseArray<>(); 208 ParallelCombination()209 ParallelCombination() { 210 } 211 212 /** 213 * Add or replace a one shot vibration effect to be performed by the specified vibrator. 214 * 215 * @param vibratorId The id of the vibrator that should perform this effect. 216 * @param effect The effect this vibrator should play. 217 * @return The {@link ParallelCombination} object to enable adding 218 * multiple effects in one chain. 219 * @see VibrationEffect#createOneShot(long, int) 220 */ 221 @NonNull addVibrator(int vibratorId, @NonNull VibrationEffect effect)222 public ParallelCombination addVibrator(int vibratorId, @NonNull VibrationEffect effect) { 223 mEffects.put(vibratorId, effect); 224 return this; 225 } 226 227 /** 228 * Combine all of the added effects into a {@link CombinedVibration}. 229 * 230 * The {@link ParallelCombination} object is still valid after this 231 * call, so you can continue adding more effects to it and generating more 232 * {@link CombinedVibration}s by calling this method again. 233 * 234 * @return The {@link CombinedVibration} resulting from combining the added effects to 235 * be played in parallel. 236 */ 237 @NonNull combine()238 public CombinedVibration combine() { 239 if (mEffects.size() == 0) { 240 throw new IllegalStateException( 241 "Combination must have at least one element to combine."); 242 } 243 CombinedVibration combined = new Stereo(mEffects); 244 combined.validate(); 245 return combined; 246 } 247 } 248 249 /** 250 * A combination of haptic effects that should be played in multiple vibrators in sequence. 251 * 252 * @hide 253 * @see CombinedVibration#startSequential() 254 */ 255 @TestApi 256 public static final class SequentialCombination { 257 258 private final ArrayList<CombinedVibration> mEffects = new ArrayList<>(); 259 private final ArrayList<Integer> mDelays = new ArrayList<>(); 260 SequentialCombination()261 SequentialCombination() { 262 } 263 264 /** 265 * Add a single vibration effect to be performed next. 266 * 267 * Similar to {@link #addNext(int, VibrationEffect, int)}, but with no delay. The effect 268 * will start playing immediately after the previous vibration is finished. 269 * 270 * @param vibratorId The id of the vibrator that should perform this effect. 271 * @param effect The effect this vibrator should play. 272 * @return The {@link CombinedVibration.SequentialCombination} object to enable adding 273 * multiple effects in one chain. 274 */ 275 @NonNull addNext(int vibratorId, @NonNull VibrationEffect effect)276 public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect) { 277 return addNext(vibratorId, effect, /* delay= */ 0); 278 } 279 280 /** 281 * Add a single vibration effect to be performed next. 282 * 283 * The delay is applied immediately after the previous vibration is finished. The effect 284 * will start playing after the delay. 285 * 286 * @param vibratorId The id of the vibrator that should perform this effect. 287 * @param effect The effect this vibrator should play. 288 * @param delay The amount of time, in milliseconds, to wait between playing the prior 289 * vibration and this one, starting at the time the previous vibration in 290 * this sequence is finished. 291 * @return The {@link CombinedVibration.SequentialCombination} object to enable adding 292 * multiple effects in one chain. 293 */ 294 @NonNull addNext(int vibratorId, @NonNull VibrationEffect effect, int delay)295 public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect, 296 int delay) { 297 return addNext( 298 CombinedVibration.startParallel().addVibrator(vibratorId, effect).combine(), 299 delay); 300 } 301 302 /** 303 * Add a combined vibration effect to be performed next. 304 * 305 * Similar to {@link #addNext(CombinedVibration, int)}, but with no delay. The effect will 306 * start playing immediately after the previous vibration is finished. 307 * 308 * @param effect The combined effect to be performed next. 309 * @return The {@link CombinedVibration.SequentialCombination} object to enable adding 310 * multiple effects in one chain. 311 * @see VibrationEffect#createOneShot(long, int) 312 */ 313 @NonNull addNext(@onNull CombinedVibration effect)314 public SequentialCombination addNext(@NonNull CombinedVibration effect) { 315 return addNext(effect, /* delay= */ 0); 316 } 317 318 /** 319 * Add a combined vibration effect to be performed next. 320 * 321 * The delay is applied immediately after the previous vibration is finished. The vibration 322 * will start playing after the delay. 323 * 324 * @param effect The combined effect to be performed next. 325 * @param delay The amount of time, in milliseconds, to wait between playing the prior 326 * vibration and this one, starting at the time the previous vibration in this 327 * sequence is finished. 328 * @return The {@link CombinedVibration.SequentialCombination} object to enable adding 329 * multiple effects in one chain. 330 */ 331 @NonNull addNext(@onNull CombinedVibration effect, int delay)332 public SequentialCombination addNext(@NonNull CombinedVibration effect, int delay) { 333 if (effect instanceof Sequential) { 334 Sequential sequentialEffect = (Sequential) effect; 335 int firstEffectIndex = mDelays.size(); 336 mEffects.addAll(sequentialEffect.getEffects()); 337 mDelays.addAll(sequentialEffect.getDelays()); 338 mDelays.set(firstEffectIndex, delay + mDelays.get(firstEffectIndex)); 339 } else { 340 mEffects.add(effect); 341 mDelays.add(delay); 342 } 343 return this; 344 } 345 346 /** 347 * Combine all of the added effects in sequence. 348 * 349 * The {@link CombinedVibration.SequentialCombination} object is still valid after 350 * this call, so you can continue adding more effects to it and generating more {@link 351 * CombinedVibration}s by calling this method again. 352 * 353 * @return The {@link CombinedVibration} resulting from combining the added effects to 354 * be played in sequence. 355 */ 356 @NonNull combine()357 public CombinedVibration combine() { 358 if (mEffects.size() == 0) { 359 throw new IllegalStateException( 360 "Combination must have at least one element to combine."); 361 } 362 CombinedVibration combined = new Sequential(mEffects, mDelays); 363 combined.validate(); 364 return combined; 365 } 366 } 367 368 /** 369 * Represents a single {@link VibrationEffect} that should be played in all vibrators at the 370 * same time. 371 * 372 * @hide 373 */ 374 @TestApi 375 public static final class Mono extends CombinedVibration { 376 private final VibrationEffect mEffect; 377 Mono(Parcel in)378 Mono(Parcel in) { 379 mEffect = VibrationEffect.CREATOR.createFromParcel(in); 380 } 381 Mono(@onNull VibrationEffect effect)382 Mono(@NonNull VibrationEffect effect) { 383 mEffect = effect; 384 } 385 386 @NonNull getEffect()387 public VibrationEffect getEffect() { 388 return mEffect; 389 } 390 391 @Override getDuration()392 public long getDuration() { 393 return mEffect.getDuration(); 394 } 395 396 /** @hide */ 397 @Override getDuration(@ullable SparseArray<VibratorInfo> vibratorInfos)398 public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) { 399 if (vibratorInfos == null) { 400 return getDuration(); 401 } 402 long maxDuration = 0; 403 for (int i = 0; i < vibratorInfos.size(); i++) { 404 long duration = mEffect.getDuration(vibratorInfos.valueAt(i)); 405 if ((duration == Long.MAX_VALUE) || (duration < 0)) { 406 return duration; 407 } 408 maxDuration = Math.max(maxDuration, duration); 409 } 410 return maxDuration; 411 } 412 413 /** @hide */ 414 @Override isHapticFeedbackCandidate()415 public boolean isHapticFeedbackCandidate() { 416 return mEffect.isHapticFeedbackCandidate(); 417 } 418 419 /** @hide */ 420 @Override validate()421 public void validate() { 422 mEffect.validate(); 423 } 424 425 /** @hide */ 426 @Override transform( VibrationEffect.Transformation<ParamT> transformation, ParamT param)427 public <ParamT> CombinedVibration transform( 428 VibrationEffect.Transformation<ParamT> transformation, ParamT param) { 429 VibrationEffect newEffect = transformation.transform(mEffect, param); 430 if (mEffect.equals(newEffect)) { 431 return this; 432 } 433 // Make sure the validate methods are triggered 434 return CombinedVibration.createParallel(newEffect); 435 } 436 437 /** @hide */ 438 @Override adapt(VibratorAdapter adapter)439 public CombinedVibration adapt(VibratorAdapter adapter) { 440 ParallelCombination combination = CombinedVibration.startParallel(); 441 boolean hasSameEffects = true; 442 for (int vibratorId : adapter.getAvailableVibratorIds()) { 443 VibrationEffect newEffect = adapter.adaptToVibrator(vibratorId, mEffect); 444 if (newEffect == null) { 445 // The vibration effect contains unsupported segments and cannot be played. 446 return null; 447 } 448 combination.addVibrator(vibratorId, newEffect); 449 hasSameEffects &= mEffect.equals(newEffect); 450 } 451 if (hasSameEffects) { 452 return this; 453 } 454 // Make sure the validate methods are triggered 455 return combination.combine(); 456 } 457 458 /** @hide */ 459 @Override hasVibrator(int vibratorId)460 public boolean hasVibrator(int vibratorId) { 461 return true; 462 } 463 464 /** @hide */ 465 @Override hasVendorEffects()466 public boolean hasVendorEffects() { 467 if (!Flags.vendorVibrationEffects()) { 468 return false; 469 } 470 return mEffect instanceof VibrationEffect.VendorEffect; 471 } 472 473 @Override equals(Object o)474 public boolean equals(Object o) { 475 if (this == o) { 476 return true; 477 } 478 if (!(o instanceof Mono)) { 479 return false; 480 } 481 Mono other = (Mono) o; 482 return mEffect.equals(other.mEffect); 483 } 484 485 @Override hashCode()486 public int hashCode() { 487 return Objects.hash(mEffect); 488 } 489 490 @Override toString()491 public String toString() { 492 return "Mono{mEffect=" + mEffect + '}'; 493 } 494 495 /** @hide */ 496 @Override toDebugString()497 public String toDebugString() { 498 // Simplify vibration string, use the single effect to represent it. 499 return mEffect.toDebugString(); 500 } 501 502 @Override describeContents()503 public int describeContents() { 504 return 0; 505 } 506 507 @Override writeToParcel(@onNull Parcel out, int flags)508 public void writeToParcel(@NonNull Parcel out, int flags) { 509 out.writeInt(PARCEL_TOKEN_MONO); 510 mEffect.writeToParcel(out, flags); 511 } 512 513 @NonNull 514 public static final Parcelable.Creator<Mono> CREATOR = 515 new Parcelable.Creator<Mono>() { 516 @Override 517 public Mono createFromParcel(@NonNull Parcel in) { 518 // Skip the type token 519 in.readInt(); 520 return new Mono(in); 521 } 522 523 @Override 524 @NonNull 525 public Mono[] newArray(int size) { 526 return new Mono[size]; 527 } 528 }; 529 } 530 531 /** 532 * Represents a set of {@link VibrationEffect VibrationEffects} associated to individual 533 * vibrators that should be played at the same time. 534 * 535 * @hide 536 */ 537 @TestApi 538 public static final class Stereo extends CombinedVibration { 539 540 /** Mapping vibrator ids to effects. */ 541 private final SparseArray<VibrationEffect> mEffects; 542 Stereo(Parcel in)543 Stereo(Parcel in) { 544 int size = in.readInt(); 545 mEffects = new SparseArray<>(size); 546 for (int i = 0; i < size; i++) { 547 int vibratorId = in.readInt(); 548 mEffects.put(vibratorId, VibrationEffect.CREATOR.createFromParcel(in)); 549 } 550 } 551 Stereo(@onNull SparseArray<VibrationEffect> effects)552 Stereo(@NonNull SparseArray<VibrationEffect> effects) { 553 mEffects = new SparseArray<>(effects.size()); 554 for (int i = 0; i < effects.size(); i++) { 555 mEffects.put(effects.keyAt(i), effects.valueAt(i)); 556 } 557 } 558 559 /** Effects to be performed in parallel, where each key represents the vibrator id. */ 560 @NonNull getEffects()561 public SparseArray<VibrationEffect> getEffects() { 562 return mEffects; 563 } 564 565 @Override getDuration()566 public long getDuration() { 567 return getDuration(idx -> mEffects.valueAt(idx).getDuration()); 568 } 569 570 /** @hide */ 571 @Override getDuration(@ullable SparseArray<VibratorInfo> vibratorInfos)572 public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) { 573 if (vibratorInfos == null) { 574 return getDuration(); 575 } 576 return getDuration(idx -> { 577 VibrationEffect effect = mEffects.valueAt(idx); 578 VibratorInfo info = vibratorInfos.get(mEffects.keyAt(idx)); 579 return effect.getDuration(info); 580 }); 581 } 582 getDuration(Function<Integer, Long> durationFn)583 private long getDuration(Function<Integer, Long> durationFn) { 584 long maxDuration = Long.MIN_VALUE; 585 boolean hasUnknownStep = false; 586 for (int i = 0; i < mEffects.size(); i++) { 587 long duration = durationFn.apply(i); 588 if (duration == Long.MAX_VALUE) { 589 // If any duration is repeating, this combination duration is also repeating. 590 return duration; 591 } 592 maxDuration = Math.max(maxDuration, duration); 593 // If any step is unknown, this combination duration will also be unknown, unless 594 // any step is repeating. Repeating vibrations take precedence over non-repeating 595 // ones in the service, so continue looping to check for repeating steps. 596 hasUnknownStep |= duration < 0; 597 } 598 if (hasUnknownStep) { 599 // If any step is unknown, this combination duration is also unknown. 600 return -1; 601 } 602 return maxDuration; 603 } 604 605 /** @hide */ 606 @Override 607 public boolean isHapticFeedbackCandidate() { 608 for (int i = 0; i < mEffects.size(); i++) { 609 if (!mEffects.valueAt(i).isHapticFeedbackCandidate()) { 610 return false; 611 } 612 } 613 return true; 614 } 615 616 /** @hide */ 617 @Override 618 public void validate() { 619 Preconditions.checkArgument(mEffects.size() > 0, 620 "There should be at least one effect set for a combined effect"); 621 for (int i = 0; i < mEffects.size(); i++) { 622 mEffects.valueAt(i).validate(); 623 } 624 } 625 626 /** @hide */ 627 @Override 628 public <ParamT> CombinedVibration transform( 629 VibrationEffect.Transformation<ParamT> transformation, ParamT param) { 630 ParallelCombination combination = CombinedVibration.startParallel(); 631 boolean hasSameEffects = true; 632 for (int i = 0; i < mEffects.size(); i++) { 633 int vibratorId = mEffects.keyAt(i); 634 VibrationEffect effect = mEffects.valueAt(i); 635 VibrationEffect newEffect = transformation.transform(effect, param); 636 combination.addVibrator(vibratorId, newEffect); 637 hasSameEffects &= effect.equals(newEffect); 638 } 639 if (hasSameEffects) { 640 return this; 641 } 642 // Make sure the validate methods are triggered 643 return combination.combine(); 644 } 645 646 /** @hide */ 647 @Override 648 public CombinedVibration adapt(VibratorAdapter adapter) { 649 ParallelCombination combination = CombinedVibration.startParallel(); 650 boolean hasSameEffects = true; 651 for (int i = 0; i < mEffects.size(); i++) { 652 int vibratorId = mEffects.keyAt(i); 653 VibrationEffect effect = mEffects.valueAt(i); 654 VibrationEffect newEffect = adapter.adaptToVibrator(vibratorId, effect); 655 if (newEffect == null) { 656 // The vibration effect contains unsupported segments and cannot be played. 657 return null; 658 } 659 combination.addVibrator(vibratorId, newEffect); 660 hasSameEffects &= effect.equals(newEffect); 661 } 662 if (hasSameEffects) { 663 return this; 664 } 665 // Make sure the validate methods are triggered 666 return combination.combine(); 667 } 668 669 /** @hide */ 670 @Override 671 public boolean hasVibrator(int vibratorId) { 672 return mEffects.indexOfKey(vibratorId) >= 0; 673 } 674 675 /** @hide */ 676 @Override 677 public boolean hasVendorEffects() { 678 if (!Flags.vendorVibrationEffects()) { 679 return false; 680 } 681 for (int i = 0; i < mEffects.size(); i++) { 682 if (mEffects.get(i) instanceof VibrationEffect.VendorEffect) { 683 return true; 684 } 685 } 686 return false; 687 } 688 689 @Override 690 public boolean equals(Object o) { 691 if (this == o) { 692 return true; 693 } 694 if (!(o instanceof Stereo)) { 695 return false; 696 } 697 Stereo other = (Stereo) o; 698 if (mEffects.size() != other.mEffects.size()) { 699 return false; 700 } 701 for (int i = 0; i < mEffects.size(); i++) { 702 if (!mEffects.valueAt(i).equals(other.mEffects.get(mEffects.keyAt(i)))) { 703 return false; 704 } 705 } 706 return true; 707 } 708 709 @Override 710 public int hashCode() { 711 return mEffects.contentHashCode(); 712 } 713 714 @Override 715 public String toString() { 716 return "Stereo{mEffects=" + mEffects + '}'; 717 } 718 719 /** @hide */ 720 @Override 721 public String toDebugString() { 722 StringJoiner sj = new StringJoiner(",", "Stereo{", "}"); 723 for (int i = 0; i < mEffects.size(); i++) { 724 sj.add(String.format(Locale.ROOT, "vibrator(id=%d): %s", 725 mEffects.keyAt(i), mEffects.valueAt(i).toDebugString())); 726 } 727 return sj.toString(); 728 } 729 730 @Override 731 public int describeContents() { 732 return 0; 733 } 734 735 @Override 736 public void writeToParcel(@NonNull Parcel out, int flags) { 737 out.writeInt(PARCEL_TOKEN_STEREO); 738 out.writeInt(mEffects.size()); 739 for (int i = 0; i < mEffects.size(); i++) { 740 out.writeInt(mEffects.keyAt(i)); 741 mEffects.valueAt(i).writeToParcel(out, flags); 742 } 743 } 744 745 @NonNull 746 public static final Parcelable.Creator<Stereo> CREATOR = 747 new Parcelable.Creator<Stereo>() { 748 @Override 749 public Stereo createFromParcel(@NonNull Parcel in) { 750 // Skip the type token 751 in.readInt(); 752 return new Stereo(in); 753 } 754 755 @Override 756 @NonNull 757 public Stereo[] newArray(int size) { 758 return new Stereo[size]; 759 } 760 }; 761 } 762 763 /** 764 * Represents a list of {@link CombinedVibration CombinedVibrations} that should be played in 765 * sequence. 766 * 767 * @hide 768 */ 769 @TestApi 770 public static final class Sequential extends CombinedVibration { 771 // If a vibration is playing more than 3 effects, it's probably not haptic feedback 772 private static final long MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE = 3; 773 774 private final List<CombinedVibration> mEffects; 775 private final List<Integer> mDelays; 776 777 Sequential(Parcel in) { 778 int size = in.readInt(); 779 mEffects = new ArrayList<>(size); 780 mDelays = new ArrayList<>(size); 781 for (int i = 0; i < size; i++) { 782 mDelays.add(in.readInt()); 783 mEffects.add(CombinedVibration.CREATOR.createFromParcel(in)); 784 } 785 } 786 787 Sequential(@NonNull List<CombinedVibration> effects, 788 @NonNull List<Integer> delays) { 789 mEffects = new ArrayList<>(effects); 790 mDelays = new ArrayList<>(delays); 791 } 792 793 /** Effects to be performed in sequence. */ 794 @NonNull 795 public List<CombinedVibration> getEffects() { 796 return mEffects; 797 } 798 799 /** Delay to be applied before each effect in {@link #getEffects()}. */ 800 @NonNull 801 public List<Integer> getDelays() { 802 return mDelays; 803 } 804 805 @Override 806 public long getDuration() { 807 return getDuration(CombinedVibration::getDuration); 808 } 809 810 /** @hide */ 811 @Override 812 public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) { 813 return getDuration(effect -> effect.getDuration(vibratorInfos)); 814 } 815 816 private long getDuration(Function<CombinedVibration, Long> durationFn) { 817 boolean hasUnknownStep = false; 818 long durations = 0; 819 final int effectCount = mEffects.size(); 820 for (int i = 0; i < effectCount; i++) { 821 long duration = durationFn.apply(mEffects.get(i)); 822 if (duration == Long.MAX_VALUE) { 823 // If any duration is repeating, this combination duration is also repeating. 824 return duration; 825 } 826 durations += duration; 827 // If any step is unknown, this combination duration will also be unknown, unless 828 // any step is repeating. Repeating vibrations take precedence over non-repeating 829 // ones in the service, so continue looping to check for repeating steps. 830 hasUnknownStep |= duration < 0; 831 } 832 if (hasUnknownStep) { 833 // If any step is unknown, this combination duration is also unknown. 834 return -1; 835 } 836 long delays = 0; 837 for (int i = 0; i < effectCount; i++) { 838 delays += mDelays.get(i); 839 } 840 return durations + delays; 841 } 842 843 /** @hide */ 844 @Override 845 public boolean isHapticFeedbackCandidate() { 846 final int effectCount = mEffects.size(); 847 if (effectCount > MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE) { 848 return false; 849 } 850 for (int i = 0; i < effectCount; i++) { 851 if (!mEffects.get(i).isHapticFeedbackCandidate()) { 852 return false; 853 } 854 } 855 return true; 856 } 857 858 /** @hide */ 859 @Override 860 public void validate() { 861 Preconditions.checkArgument(mEffects.size() > 0, 862 "There should be at least one effect set for a combined effect"); 863 Preconditions.checkArgument(mEffects.size() == mDelays.size(), 864 "Effect and delays should have equal length"); 865 final int effectCount = mEffects.size(); 866 for (int i = 0; i < effectCount; i++) { 867 if (mDelays.get(i) < 0) { 868 throw new IllegalArgumentException("Delays must all be >= 0" 869 + " (delays=" + mDelays + ")"); 870 } 871 } 872 for (int i = 0; i < effectCount; i++) { 873 CombinedVibration effect = mEffects.get(i); 874 if (effect instanceof Sequential) { 875 throw new IllegalArgumentException( 876 "There should be no nested sequential effects in a combined effect"); 877 } 878 effect.validate(); 879 } 880 } 881 882 /** @hide */ 883 @Override 884 public <ParamT> CombinedVibration transform( 885 VibrationEffect.Transformation<ParamT> transformation, ParamT param) { 886 SequentialCombination combination = CombinedVibration.startSequential(); 887 boolean hasSameEffects = true; 888 for (int i = 0; i < mEffects.size(); i++) { 889 CombinedVibration vibration = mEffects.get(i); 890 CombinedVibration newVibration = vibration.transform(transformation, param); 891 combination.addNext(newVibration, mDelays.get(i)); 892 hasSameEffects &= vibration.equals(newVibration); 893 } 894 if (hasSameEffects) { 895 return this; 896 } 897 // Make sure the validate methods are triggered 898 return combination.combine(); 899 } 900 901 /** @hide */ 902 @Override 903 public CombinedVibration adapt(VibratorAdapter adapter) { 904 SequentialCombination combination = CombinedVibration.startSequential(); 905 boolean hasSameEffects = true; 906 for (int i = 0; i < mEffects.size(); i++) { 907 CombinedVibration vibration = mEffects.get(i); 908 CombinedVibration newVibration = vibration.adapt(adapter); 909 combination.addNext(newVibration, mDelays.get(i)); 910 hasSameEffects &= vibration.equals(newVibration); 911 } 912 if (hasSameEffects) { 913 return this; 914 } 915 // Make sure the validate methods are triggered 916 return combination.combine(); 917 } 918 919 /** @hide */ 920 @Override 921 public boolean hasVibrator(int vibratorId) { 922 final int effectCount = mEffects.size(); 923 for (int i = 0; i < effectCount; i++) { 924 if (mEffects.get(i).hasVibrator(vibratorId)) { 925 return true; 926 } 927 } 928 return false; 929 } 930 931 /** @hide */ 932 @Override 933 public boolean hasVendorEffects() { 934 for (int i = 0; i < mEffects.size(); i++) { 935 if (mEffects.get(i).hasVendorEffects()) { 936 return true; 937 } 938 } 939 return false; 940 } 941 942 @Override 943 public boolean equals(Object o) { 944 if (this == o) { 945 return true; 946 } 947 if (!(o instanceof Sequential)) { 948 return false; 949 } 950 Sequential other = (Sequential) o; 951 return mDelays.equals(other.mDelays) && mEffects.equals(other.mEffects); 952 } 953 954 @Override 955 public int hashCode() { 956 return Objects.hash(mEffects, mDelays); 957 } 958 959 @Override 960 public String toString() { 961 return "Sequential{mEffects=" + mEffects + ", mDelays=" + mDelays + '}'; 962 } 963 964 /** @hide */ 965 @Override 966 public String toDebugString() { 967 StringJoiner sj = new StringJoiner(",", "Sequential{", "}"); 968 for (int i = 0; i < mEffects.size(); i++) { 969 sj.add(String.format(Locale.ROOT, "delayMs=%d, effect=%s", 970 mDelays.get(i), mEffects.get(i).toDebugString())); 971 } 972 return sj.toString(); 973 } 974 975 @Override 976 public int describeContents() { 977 return 0; 978 } 979 980 @Override 981 public void writeToParcel(@NonNull Parcel out, int flags) { 982 out.writeInt(PARCEL_TOKEN_SEQUENTIAL); 983 out.writeInt(mEffects.size()); 984 for (int i = 0; i < mEffects.size(); i++) { 985 out.writeInt(mDelays.get(i)); 986 mEffects.get(i).writeToParcel(out, flags); 987 } 988 } 989 990 @NonNull 991 public static final Parcelable.Creator<Sequential> CREATOR = 992 new Parcelable.Creator<Sequential>() { 993 @Override 994 public Sequential createFromParcel(@NonNull Parcel in) { 995 // Skip the type token 996 in.readInt(); 997 return new Sequential(in); 998 } 999 1000 @Override 1001 @NonNull 1002 public Sequential[] newArray(int size) { 1003 return new Sequential[size]; 1004 } 1005 }; 1006 } 1007 1008 @NonNull 1009 public static final Parcelable.Creator<CombinedVibration> CREATOR = 1010 new Parcelable.Creator<CombinedVibration>() { 1011 @Override 1012 public CombinedVibration createFromParcel(Parcel in) { 1013 int token = in.readInt(); 1014 if (token == PARCEL_TOKEN_MONO) { 1015 return new Mono(in); 1016 } else if (token == PARCEL_TOKEN_STEREO) { 1017 return new Stereo(in); 1018 } else if (token == PARCEL_TOKEN_SEQUENTIAL) { 1019 return new Sequential(in); 1020 } else { 1021 throw new IllegalStateException( 1022 "Unexpected combined vibration event type token in parcel."); 1023 } 1024 } 1025 1026 @Override 1027 public CombinedVibration[] newArray(int size) { 1028 return new CombinedVibration[size]; 1029 } 1030 }; 1031 } 1032