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.TestApi; 21 import android.util.SparseArray; 22 23 import com.android.internal.util.Preconditions; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.Objects; 28 29 /** 30 * A CombinedVibration describes a combination of haptic effects to be performed by one or more 31 * {@link Vibrator Vibrators}. 32 * 33 * These effects may be any number of things, from single shot vibrations to complex waveforms. 34 * 35 * @see VibrationEffect 36 */ 37 @SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here. 38 public abstract class CombinedVibration implements Parcelable { 39 private static final int PARCEL_TOKEN_MONO = 1; 40 private static final int PARCEL_TOKEN_STEREO = 2; 41 private static final int PARCEL_TOKEN_SEQUENTIAL = 3; 42 43 /** Prevent subclassing from outside of the framework. */ CombinedVibration()44 CombinedVibration() { 45 } 46 47 /** 48 * Create a vibration that plays a single effect in parallel on all vibrators. 49 * 50 * A parallel vibration that takes a single {@link VibrationEffect} to be performed by multiple 51 * vibrators at the same time. 52 * 53 * @param effect The {@link VibrationEffect} to perform. 54 * @return The combined vibration representing the single effect to be played in all vibrators. 55 */ 56 @NonNull createParallel(@onNull VibrationEffect effect)57 public static CombinedVibration createParallel(@NonNull VibrationEffect effect) { 58 CombinedVibration combined = new Mono(effect); 59 combined.validate(); 60 return combined; 61 } 62 63 /** 64 * Start creating a vibration that plays effects in parallel on one or more vibrators. 65 * 66 * A parallel vibration takes one or more {@link VibrationEffect VibrationEffects} associated to 67 * individual vibrators to be performed at the same time. 68 * 69 * @see CombinedVibration.ParallelCombination 70 */ 71 @NonNull startParallel()72 public static ParallelCombination startParallel() { 73 return new ParallelCombination(); 74 } 75 76 /** 77 * Start creating a vibration that plays effects in sequence on one or more vibrators. 78 * 79 * A sequential vibration takes one or more {@link CombinedVibration CombinedVibrations} to be 80 * performed by one or more vibrators in order. Each {@link CombinedVibration} starts only after 81 * the previous one is finished. 82 * 83 * @hide 84 * @see CombinedVibration.SequentialCombination 85 */ 86 @TestApi 87 @NonNull startSequential()88 public static SequentialCombination startSequential() { 89 return new SequentialCombination(); 90 } 91 92 @Override describeContents()93 public int describeContents() { 94 return 0; 95 } 96 97 /** 98 * Gets the estimated duration of the combined vibration in milliseconds. 99 * 100 * <p>For parallel combinations this means the maximum duration of any individual {@link 101 * VibrationEffect}. For sequential combinations, this is a sum of each step and delays. 102 * 103 * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative 104 * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g. 105 * Prebaked effects where the length is device and potentially run-time dependent), this returns 106 * -1. 107 * 108 * @hide 109 */ 110 @TestApi getDuration()111 public abstract long getDuration(); 112 113 /** @hide */ validate()114 public abstract void validate(); 115 116 /** @hide */ hasVibrator(int vibratorId)117 public abstract boolean hasVibrator(int vibratorId); 118 119 /** 120 * A combination of haptic effects that should be played in multiple vibrators in parallel. 121 * 122 * @see CombinedVibration#startParallel() 123 */ 124 public static final class ParallelCombination { 125 126 private final SparseArray<VibrationEffect> mEffects = new SparseArray<>(); 127 ParallelCombination()128 ParallelCombination() { 129 } 130 131 /** 132 * Add or replace a one shot vibration effect to be performed by the specified vibrator. 133 * 134 * @param vibratorId The id of the vibrator that should perform this effect. 135 * @param effect The effect this vibrator should play. 136 * @return The {@link ParallelCombination} object to enable adding 137 * multiple effects in one chain. 138 * @see VibrationEffect#createOneShot(long, int) 139 */ 140 @NonNull addVibrator(int vibratorId, @NonNull VibrationEffect effect)141 public ParallelCombination addVibrator(int vibratorId, @NonNull VibrationEffect effect) { 142 mEffects.put(vibratorId, effect); 143 return this; 144 } 145 146 /** 147 * Combine all of the added effects into a {@link CombinedVibration}. 148 * 149 * The {@link ParallelCombination} object is still valid after this 150 * call, so you can continue adding more effects to it and generating more 151 * {@link CombinedVibration}s by calling this method again. 152 * 153 * @return The {@link CombinedVibration} resulting from combining the added effects to 154 * be played in parallel. 155 */ 156 @NonNull combine()157 public CombinedVibration combine() { 158 if (mEffects.size() == 0) { 159 throw new IllegalStateException( 160 "Combination must have at least one element to combine."); 161 } 162 CombinedVibration combined = new Stereo(mEffects); 163 combined.validate(); 164 return combined; 165 } 166 } 167 168 /** 169 * A combination of haptic effects that should be played in multiple vibrators in sequence. 170 * 171 * @hide 172 * @see CombinedVibration#startSequential() 173 */ 174 @TestApi 175 public static final class SequentialCombination { 176 177 private final ArrayList<CombinedVibration> mEffects = new ArrayList<>(); 178 private final ArrayList<Integer> mDelays = new ArrayList<>(); 179 SequentialCombination()180 SequentialCombination() { 181 } 182 183 /** 184 * Add a single vibration effect to be performed next. 185 * 186 * Similar to {@link #addNext(int, VibrationEffect, int)}, but with no delay. The effect 187 * will start playing immediately after the previous vibration is finished. 188 * 189 * @param vibratorId The id of the vibrator that should perform this effect. 190 * @param effect The effect this vibrator should play. 191 * @return The {@link CombinedVibration.SequentialCombination} object to enable adding 192 * multiple effects in one chain. 193 */ 194 @NonNull addNext(int vibratorId, @NonNull VibrationEffect effect)195 public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect) { 196 return addNext(vibratorId, effect, /* delay= */ 0); 197 } 198 199 /** 200 * Add a single vibration effect to be performed next. 201 * 202 * The delay is applied immediately after the previous vibration is finished. The effect 203 * will start playing after the delay. 204 * 205 * @param vibratorId The id of the vibrator that should perform this effect. 206 * @param effect The effect this vibrator should play. 207 * @param delay The amount of time, in milliseconds, to wait between playing the prior 208 * vibration and this one, starting at the time the previous vibration in 209 * this sequence is finished. 210 * @return The {@link CombinedVibration.SequentialCombination} object to enable adding 211 * multiple effects in one chain. 212 */ 213 @NonNull addNext(int vibratorId, @NonNull VibrationEffect effect, int delay)214 public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect, 215 int delay) { 216 return addNext( 217 CombinedVibration.startParallel().addVibrator(vibratorId, effect).combine(), 218 delay); 219 } 220 221 /** 222 * Add a combined vibration effect to be performed next. 223 * 224 * Similar to {@link #addNext(CombinedVibration, int)}, but with no delay. The effect will 225 * start playing immediately after the previous vibration is finished. 226 * 227 * @param effect The combined effect to be performed next. 228 * @return The {@link CombinedVibration.SequentialCombination} object to enable adding 229 * multiple effects in one chain. 230 * @see VibrationEffect#createOneShot(long, int) 231 */ 232 @NonNull addNext(@onNull CombinedVibration effect)233 public SequentialCombination addNext(@NonNull CombinedVibration effect) { 234 return addNext(effect, /* delay= */ 0); 235 } 236 237 /** 238 * Add a combined vibration effect to be performed next. 239 * 240 * The delay is applied immediately after the previous vibration is finished. The vibration 241 * will start playing after the delay. 242 * 243 * @param effect The combined effect to be performed next. 244 * @param delay The amount of time, in milliseconds, to wait between playing the prior 245 * vibration and this one, starting at the time the previous vibration in this 246 * sequence is finished. 247 * @return The {@link CombinedVibration.SequentialCombination} object to enable adding 248 * multiple effects in one chain. 249 */ 250 @NonNull addNext(@onNull CombinedVibration effect, int delay)251 public SequentialCombination addNext(@NonNull CombinedVibration effect, int delay) { 252 if (effect instanceof Sequential) { 253 Sequential sequentialEffect = (Sequential) effect; 254 int firstEffectIndex = mDelays.size(); 255 mEffects.addAll(sequentialEffect.getEffects()); 256 mDelays.addAll(sequentialEffect.getDelays()); 257 mDelays.set(firstEffectIndex, delay + mDelays.get(firstEffectIndex)); 258 } else { 259 mEffects.add(effect); 260 mDelays.add(delay); 261 } 262 return this; 263 } 264 265 /** 266 * Combine all of the added effects in sequence. 267 * 268 * The {@link CombinedVibration.SequentialCombination} object is still valid after 269 * this call, so you can continue adding more effects to it and generating more {@link 270 * CombinedVibration}s by calling this method again. 271 * 272 * @return The {@link CombinedVibration} resulting from combining the added effects to 273 * be played in sequence. 274 */ 275 @NonNull combine()276 public CombinedVibration combine() { 277 if (mEffects.size() == 0) { 278 throw new IllegalStateException( 279 "Combination must have at least one element to combine."); 280 } 281 CombinedVibration combined = new Sequential(mEffects, mDelays); 282 combined.validate(); 283 return combined; 284 } 285 } 286 287 /** 288 * Represents a single {@link VibrationEffect} that should be played in all vibrators at the 289 * same time. 290 * 291 * @hide 292 */ 293 @TestApi 294 public static final class Mono extends CombinedVibration { 295 private final VibrationEffect mEffect; 296 Mono(Parcel in)297 Mono(Parcel in) { 298 mEffect = VibrationEffect.CREATOR.createFromParcel(in); 299 } 300 Mono(@onNull VibrationEffect effect)301 Mono(@NonNull VibrationEffect effect) { 302 mEffect = effect; 303 } 304 305 @NonNull getEffect()306 public VibrationEffect getEffect() { 307 return mEffect; 308 } 309 310 @Override getDuration()311 public long getDuration() { 312 return mEffect.getDuration(); 313 } 314 315 /** @hide */ 316 @Override validate()317 public void validate() { 318 mEffect.validate(); 319 } 320 321 /** @hide */ 322 @Override hasVibrator(int vibratorId)323 public boolean hasVibrator(int vibratorId) { 324 return true; 325 } 326 327 @Override equals(Object o)328 public boolean equals(Object o) { 329 if (!(o instanceof Mono)) { 330 return false; 331 } 332 Mono other = (Mono) o; 333 return mEffect.equals(other.mEffect); 334 } 335 336 @Override hashCode()337 public int hashCode() { 338 return Objects.hash(mEffect); 339 } 340 341 @Override toString()342 public String toString() { 343 return "Mono{mEffect=" + mEffect + '}'; 344 } 345 346 @Override describeContents()347 public int describeContents() { 348 return 0; 349 } 350 351 @Override writeToParcel(@onNull Parcel out, int flags)352 public void writeToParcel(@NonNull Parcel out, int flags) { 353 out.writeInt(PARCEL_TOKEN_MONO); 354 mEffect.writeToParcel(out, flags); 355 } 356 357 @NonNull 358 public static final Parcelable.Creator<Mono> CREATOR = 359 new Parcelable.Creator<Mono>() { 360 @Override 361 public Mono createFromParcel(@NonNull Parcel in) { 362 // Skip the type token 363 in.readInt(); 364 return new Mono(in); 365 } 366 367 @Override 368 @NonNull 369 public Mono[] newArray(int size) { 370 return new Mono[size]; 371 } 372 }; 373 } 374 375 /** 376 * Represents a set of {@link VibrationEffect VibrationEffects} associated to individual 377 * vibrators that should be played at the same time. 378 * 379 * @hide 380 */ 381 @TestApi 382 public static final class Stereo extends CombinedVibration { 383 384 /** Mapping vibrator ids to effects. */ 385 private final SparseArray<VibrationEffect> mEffects; 386 Stereo(Parcel in)387 Stereo(Parcel in) { 388 int size = in.readInt(); 389 mEffects = new SparseArray<>(size); 390 for (int i = 0; i < size; i++) { 391 int vibratorId = in.readInt(); 392 mEffects.put(vibratorId, VibrationEffect.CREATOR.createFromParcel(in)); 393 } 394 } 395 Stereo(@onNull SparseArray<VibrationEffect> effects)396 Stereo(@NonNull SparseArray<VibrationEffect> effects) { 397 mEffects = new SparseArray<>(effects.size()); 398 for (int i = 0; i < effects.size(); i++) { 399 mEffects.put(effects.keyAt(i), effects.valueAt(i)); 400 } 401 } 402 403 /** Effects to be performed in parallel, where each key represents the vibrator id. */ 404 @NonNull getEffects()405 public SparseArray<VibrationEffect> getEffects() { 406 return mEffects; 407 } 408 409 @Override getDuration()410 public long getDuration() { 411 long maxDuration = Long.MIN_VALUE; 412 boolean hasUnknownStep = false; 413 for (int i = 0; i < mEffects.size(); i++) { 414 long duration = mEffects.valueAt(i).getDuration(); 415 if (duration == Long.MAX_VALUE) { 416 // If any duration is repeating, this combination duration is also repeating. 417 return duration; 418 } 419 maxDuration = Math.max(maxDuration, duration); 420 // If any step is unknown, this combination duration will also be unknown, unless 421 // any step is repeating. Repeating vibrations take precedence over non-repeating 422 // ones in the service, so continue looping to check for repeating steps. 423 hasUnknownStep |= duration < 0; 424 } 425 if (hasUnknownStep) { 426 // If any step is unknown, this combination duration is also unknown. 427 return -1; 428 } 429 return maxDuration; 430 } 431 432 /** @hide */ 433 @Override 434 public void validate() { 435 Preconditions.checkArgument(mEffects.size() > 0, 436 "There should be at least one effect set for a combined effect"); 437 for (int i = 0; i < mEffects.size(); i++) { 438 mEffects.valueAt(i).validate(); 439 } 440 } 441 442 /** @hide */ 443 @Override hasVibrator(int vibratorId)444 public boolean hasVibrator(int vibratorId) { 445 return mEffects.indexOfKey(vibratorId) >= 0; 446 } 447 448 @Override equals(Object o)449 public boolean equals(Object o) { 450 if (!(o instanceof Stereo)) { 451 return false; 452 } 453 Stereo other = (Stereo) o; 454 if (mEffects.size() != other.mEffects.size()) { 455 return false; 456 } 457 for (int i = 0; i < mEffects.size(); i++) { 458 if (!mEffects.valueAt(i).equals(other.mEffects.get(mEffects.keyAt(i)))) { 459 return false; 460 } 461 } 462 return true; 463 } 464 465 @Override hashCode()466 public int hashCode() { 467 return mEffects.contentHashCode(); 468 } 469 470 @Override toString()471 public String toString() { 472 return "Stereo{mEffects=" + mEffects + '}'; 473 } 474 475 @Override describeContents()476 public int describeContents() { 477 return 0; 478 } 479 480 @Override writeToParcel(@onNull Parcel out, int flags)481 public void writeToParcel(@NonNull Parcel out, int flags) { 482 out.writeInt(PARCEL_TOKEN_STEREO); 483 out.writeInt(mEffects.size()); 484 for (int i = 0; i < mEffects.size(); i++) { 485 out.writeInt(mEffects.keyAt(i)); 486 mEffects.valueAt(i).writeToParcel(out, flags); 487 } 488 } 489 490 @NonNull 491 public static final Parcelable.Creator<Stereo> CREATOR = 492 new Parcelable.Creator<Stereo>() { 493 @Override 494 public Stereo createFromParcel(@NonNull Parcel in) { 495 // Skip the type token 496 in.readInt(); 497 return new Stereo(in); 498 } 499 500 @Override 501 @NonNull 502 public Stereo[] newArray(int size) { 503 return new Stereo[size]; 504 } 505 }; 506 } 507 508 /** 509 * Represents a list of {@link CombinedVibration CombinedVibrations} that should be played in 510 * sequence. 511 * 512 * @hide 513 */ 514 @TestApi 515 public static final class Sequential extends CombinedVibration { 516 private final List<CombinedVibration> mEffects; 517 private final List<Integer> mDelays; 518 Sequential(Parcel in)519 Sequential(Parcel in) { 520 int size = in.readInt(); 521 mEffects = new ArrayList<>(size); 522 mDelays = new ArrayList<>(size); 523 for (int i = 0; i < size; i++) { 524 mDelays.add(in.readInt()); 525 mEffects.add(CombinedVibration.CREATOR.createFromParcel(in)); 526 } 527 } 528 Sequential(@onNull List<CombinedVibration> effects, @NonNull List<Integer> delays)529 Sequential(@NonNull List<CombinedVibration> effects, 530 @NonNull List<Integer> delays) { 531 mEffects = new ArrayList<>(effects); 532 mDelays = new ArrayList<>(delays); 533 } 534 535 /** Effects to be performed in sequence. */ 536 @NonNull getEffects()537 public List<CombinedVibration> getEffects() { 538 return mEffects; 539 } 540 541 /** Delay to be applied before each effect in {@link #getEffects()}. */ 542 @NonNull getDelays()543 public List<Integer> getDelays() { 544 return mDelays; 545 } 546 547 @Override getDuration()548 public long getDuration() { 549 boolean hasUnknownStep = false; 550 long durations = 0; 551 final int effectCount = mEffects.size(); 552 for (int i = 0; i < effectCount; i++) { 553 CombinedVibration effect = mEffects.get(i); 554 long duration = effect.getDuration(); 555 if (duration == Long.MAX_VALUE) { 556 // If any duration is repeating, this combination duration is also repeating. 557 return duration; 558 } 559 durations += duration; 560 // If any step is unknown, this combination duration will also be unknown, unless 561 // any step is repeating. Repeating vibrations take precedence over non-repeating 562 // ones in the service, so continue looping to check for repeating steps. 563 hasUnknownStep |= duration < 0; 564 } 565 if (hasUnknownStep) { 566 // If any step is unknown, this combination duration is also unknown. 567 return -1; 568 } 569 long delays = 0; 570 for (int i = 0; i < effectCount; i++) { 571 delays += mDelays.get(i); 572 } 573 return durations + delays; 574 } 575 576 /** @hide */ 577 @Override 578 public void validate() { 579 Preconditions.checkArgument(mEffects.size() > 0, 580 "There should be at least one effect set for a combined effect"); 581 Preconditions.checkArgument(mEffects.size() == mDelays.size(), 582 "Effect and delays should have equal length"); 583 final int effectCount = mEffects.size(); 584 for (int i = 0; i < effectCount; i++) { 585 if (mDelays.get(i) < 0) { 586 throw new IllegalArgumentException("Delays must all be >= 0" 587 + " (delays=" + mDelays + ")"); 588 } 589 } 590 for (int i = 0; i < effectCount; i++) { 591 CombinedVibration effect = mEffects.get(i); 592 if (effect instanceof Sequential) { 593 throw new IllegalArgumentException( 594 "There should be no nested sequential effects in a combined effect"); 595 } 596 effect.validate(); 597 } 598 } 599 600 /** @hide */ 601 @Override 602 public boolean hasVibrator(int vibratorId) { 603 final int effectCount = mEffects.size(); 604 for (int i = 0; i < effectCount; i++) { 605 if (mEffects.get(i).hasVibrator(vibratorId)) { 606 return true; 607 } 608 } 609 return false; 610 } 611 612 @Override 613 public boolean equals(Object o) { 614 if (!(o instanceof Sequential)) { 615 return false; 616 } 617 Sequential other = (Sequential) o; 618 return mDelays.equals(other.mDelays) && mEffects.equals(other.mEffects); 619 } 620 621 @Override 622 public int hashCode() { 623 return Objects.hash(mEffects, mDelays); 624 } 625 626 @Override 627 public String toString() { 628 return "Sequential{mEffects=" + mEffects + ", mDelays=" + mDelays + '}'; 629 } 630 631 @Override 632 public int describeContents() { 633 return 0; 634 } 635 636 @Override 637 public void writeToParcel(@NonNull Parcel out, int flags) { 638 out.writeInt(PARCEL_TOKEN_SEQUENTIAL); 639 out.writeInt(mEffects.size()); 640 for (int i = 0; i < mEffects.size(); i++) { 641 out.writeInt(mDelays.get(i)); 642 mEffects.get(i).writeToParcel(out, flags); 643 } 644 } 645 646 @NonNull 647 public static final Parcelable.Creator<Sequential> CREATOR = 648 new Parcelable.Creator<Sequential>() { 649 @Override 650 public Sequential createFromParcel(@NonNull Parcel in) { 651 // Skip the type token 652 in.readInt(); 653 return new Sequential(in); 654 } 655 656 @Override 657 @NonNull 658 public Sequential[] newArray(int size) { 659 return new Sequential[size]; 660 } 661 }; 662 } 663 664 @NonNull 665 public static final Parcelable.Creator<CombinedVibration> CREATOR = 666 new Parcelable.Creator<CombinedVibration>() { 667 @Override 668 public CombinedVibration createFromParcel(Parcel in) { 669 int token = in.readInt(); 670 if (token == PARCEL_TOKEN_MONO) { 671 return new Mono(in); 672 } else if (token == PARCEL_TOKEN_STEREO) { 673 return new Stereo(in); 674 } else if (token == PARCEL_TOKEN_SEQUENTIAL) { 675 return new Sequential(in); 676 } else { 677 throw new IllegalStateException( 678 "Unexpected combined vibration event type token in parcel."); 679 } 680 } 681 682 @Override 683 public CombinedVibration[] newArray(int size) { 684 return new CombinedVibration[size]; 685 } 686 }; 687 } 688