Home | History | Annotate | Download | only in os
      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.IntDef;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.annotation.TestApi;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.hardware.vibrator.V1_0.EffectStrength;
     26 import android.hardware.vibrator.V1_3.Effect;
     27 import android.net.Uri;
     28 import android.util.MathUtils;
     29 
     30 import java.lang.annotation.Retention;
     31 import java.lang.annotation.RetentionPolicy;
     32 import java.util.Arrays;
     33 
     34 /**
     35  * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
     36  *
     37  * These effects may be any number of things, from single shot vibrations to complex waveforms.
     38  */
     39 public abstract class VibrationEffect implements Parcelable {
     40     private static final int PARCEL_TOKEN_ONE_SHOT = 1;
     41     private static final int PARCEL_TOKEN_WAVEFORM = 2;
     42     private static final int PARCEL_TOKEN_EFFECT = 3;
     43 
     44     /**
     45      * The default vibration strength of the device.
     46      */
     47     public static final int DEFAULT_AMPLITUDE = -1;
     48 
     49     /**
     50      * The maximum amplitude value
     51      * @hide
     52      */
     53     public static final int MAX_AMPLITUDE = 255;
     54 
     55     /**
     56      * A click effect.
     57      *
     58      * @see #get(int)
     59      */
     60     public static final int EFFECT_CLICK = Effect.CLICK;
     61 
     62     /**
     63      * A double click effect.
     64      *
     65      * @see #get(int)
     66      */
     67     public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
     68 
     69     /**
     70      * A tick effect.
     71      * @see #get(int)
     72      */
     73     public static final int EFFECT_TICK = Effect.TICK;
     74 
     75     /**
     76      * A thud effect.
     77      * @see #get(int)
     78      * @hide
     79      */
     80     @TestApi
     81     public static final int EFFECT_THUD = Effect.THUD;
     82 
     83     /**
     84      * A pop effect.
     85      * @see #get(int)
     86      * @hide
     87      */
     88     @TestApi
     89     public static final int EFFECT_POP = Effect.POP;
     90 
     91     /**
     92      * A heavy click effect.
     93      * @see #get(int)
     94      */
     95     public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
     96 
     97     /**
     98      * A texture effect meant to replicate soft ticks.
     99      *
    100      * Unlike normal effects, texture effects are meant to be called repeatedly, generally in
    101      * response to some motion, in order to replicate the feeling of some texture underneath the
    102      * user's fingers.
    103      *
    104      * @see #get(int)
    105      * @hide
    106      */
    107     @TestApi
    108     public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK;
    109 
    110     /** {@hide} */
    111     @TestApi
    112     public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
    113 
    114     /** {@hide} */
    115     @TestApi
    116     public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
    117 
    118     /** {@hide} */
    119     @TestApi
    120     public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
    121 
    122     /**
    123      * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
    124      * pattern that can be played as a ringtone with any audio, depending on the device.
    125      *
    126      * @see #get(Uri, Context)
    127      * @hide
    128      */
    129     @TestApi
    130     public static final int[] RINGTONES = {
    131         Effect.RINGTONE_1,
    132         Effect.RINGTONE_2,
    133         Effect.RINGTONE_3,
    134         Effect.RINGTONE_4,
    135         Effect.RINGTONE_5,
    136         Effect.RINGTONE_6,
    137         Effect.RINGTONE_7,
    138         Effect.RINGTONE_8,
    139         Effect.RINGTONE_9,
    140         Effect.RINGTONE_10,
    141         Effect.RINGTONE_11,
    142         Effect.RINGTONE_12,
    143         Effect.RINGTONE_13,
    144         Effect.RINGTONE_14,
    145         Effect.RINGTONE_15
    146     };
    147 
    148     /** @hide */
    149     @IntDef(prefix = { "EFFECT_" }, value = {
    150             EFFECT_TICK,
    151             EFFECT_CLICK,
    152             EFFECT_HEAVY_CLICK,
    153             EFFECT_DOUBLE_CLICK,
    154     })
    155     @Retention(RetentionPolicy.SOURCE)
    156     public @interface EffectType {}
    157 
    158     /** @hide to prevent subclassing from outside of the framework */
    159     public VibrationEffect() { }
    160 
    161     /**
    162      * Create a one shot vibration.
    163      *
    164      * One shot vibrations will vibrate constantly for the specified period of time at the
    165      * specified amplitude, and then stop.
    166      *
    167      * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
    168      * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
    169      * {@link #DEFAULT_AMPLITUDE}.
    170      *
    171      * @return The desired effect.
    172      */
    173     public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
    174         VibrationEffect effect = new OneShot(milliseconds, amplitude);
    175         effect.validate();
    176         return effect;
    177     }
    178 
    179     /**
    180      * Create a waveform vibration.
    181      *
    182      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
    183      * each pair, the value in the amplitude array determines the strength of the vibration and the
    184      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
    185      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
    186      * <p>
    187      * The amplitude array of the generated waveform will be the same size as the given
    188      * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
    189      * starting with 0. Therefore the first timing value will be the period to wait before turning
    190      * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
    191      * strength, etc.
    192      * </p><p>
    193      * To cause the pattern to repeat, pass the index into the timings array at which to start the
    194      * repetition, or -1 to disable repeating.
    195      * </p>
    196      *
    197      * @param timings The pattern of alternating on-off timings, starting with off. Timing values
    198      *                of 0 will cause the timing / amplitude pair to be ignored.
    199      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
    200      *               want to repeat.
    201      *
    202      * @return The desired effect.
    203      */
    204     public static VibrationEffect createWaveform(long[] timings, int repeat) {
    205         int[] amplitudes = new int[timings.length];
    206         for (int i = 0; i < (timings.length / 2); i++) {
    207             amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
    208         }
    209         return createWaveform(timings, amplitudes, repeat);
    210     }
    211 
    212     /**
    213      * Create a waveform vibration.
    214      *
    215      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
    216      * each pair, the value in the amplitude array determines the strength of the vibration and the
    217      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
    218      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
    219      * </p><p>
    220      * To cause the pattern to repeat, pass the index into the timings array at which to start the
    221      * repetition, or -1 to disable repeating.
    222      * </p>
    223      *
    224      * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
    225      *                will cause the pair to be ignored.
    226      * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
    227      *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
    228      *                   amplitude value of 0 implies the motor is off.
    229      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
    230      *               want to repeat.
    231      *
    232      * @return The desired effect.
    233      */
    234     public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
    235         VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
    236         effect.validate();
    237         return effect;
    238     }
    239 
    240     /**
    241      * Create a predefined vibration effect.
    242      *
    243      * Predefined effects are a set of common vibration effects that should be identical, regardless
    244      * of the app they come from, in order to provide a cohesive experience for users across
    245      * the entire device. They also may be custom tailored to the device hardware in order to
    246      * provide a better experience than you could otherwise build using the generic building
    247      * blocks.
    248      *
    249      * This will fallback to a generic pattern if one exists and there does not exist a
    250      * hardware-specific implementation of the effect.
    251      *
    252      * @param effectId The ID of the effect to perform:
    253      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
    254      *
    255      * @return The desired effect.
    256      */
    257     @NonNull
    258     public static VibrationEffect createPredefined(@EffectType int effectId) {
    259         return get(effectId, true);
    260     }
    261 
    262     /**
    263      * Get a predefined vibration effect.
    264      *
    265      * Predefined effects are a set of common vibration effects that should be identical, regardless
    266      * of the app they come from, in order to provide a cohesive experience for users across
    267      * the entire device. They also may be custom tailored to the device hardware in order to
    268      * provide a better experience than you could otherwise build using the generic building
    269      * blocks.
    270      *
    271      * This will fallback to a generic pattern if one exists and there does not exist a
    272      * hardware-specific implementation of the effect.
    273      *
    274      * @param effectId The ID of the effect to perform:
    275      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
    276      *
    277      * @return The desired effect.
    278      * @hide
    279      */
    280     @TestApi
    281     public static VibrationEffect get(int effectId) {
    282         return get(effectId, true);
    283     }
    284 
    285     /**
    286      * Get a predefined vibration effect.
    287      *
    288      * Predefined effects are a set of common vibration effects that should be identical, regardless
    289      * of the app they come from, in order to provide a cohesive experience for users across
    290      * the entire device. They also may be custom tailored to the device hardware in order to
    291      * provide a better experience than you could otherwise build using the generic building
    292      * blocks.
    293      *
    294      * Some effects you may only want to play if there's a hardware specific implementation because
    295      * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
    296      * parameter allows you to decide whether you want to fallback to the generic implementation or
    297      * only play if there's a tuned, hardware specific one available.
    298      *
    299      * @param effectId The ID of the effect to perform:
    300      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
    301      * @param fallback Whether to fallback to a generic pattern if a hardware specific
    302      *                 implementation doesn't exist.
    303      *
    304      * @return The desired effect.
    305      * @hide
    306      */
    307     @TestApi
    308     public static VibrationEffect get(int effectId, boolean fallback) {
    309         VibrationEffect effect = new Prebaked(effectId, fallback);
    310         effect.validate();
    311         return effect;
    312     }
    313 
    314     /**
    315      * Get a predefined vibration effect associated with a given URI.
    316      *
    317      * Predefined effects are a set of common vibration effects that should be identical, regardless
    318      * of the app they come from, in order to provide a cohesive experience for users across
    319      * the entire device. They also may be custom tailored to the device hardware in order to
    320      * provide a better experience than you could otherwise build using the generic building
    321      * blocks.
    322      *
    323      * @param uri The URI associated with the haptic effect.
    324      * @param context The context used to get the URI to haptic effect association.
    325      *
    326      * @return The desired effect, or {@code null} if there's no associated effect.
    327      *
    328      * @hide
    329      */
    330     @TestApi
    331     @Nullable
    332     public static VibrationEffect get(Uri uri, Context context) {
    333         final ContentResolver cr = context.getContentResolver();
    334         Uri uncanonicalUri = cr.uncanonicalize(uri);
    335         if (uncanonicalUri == null) {
    336             // If we already had an uncanonical URI, it's possible we'll get null back here. In
    337             // this case, just use the URI as passed in since it wasn't canonicalized in the first
    338             // place.
    339             uncanonicalUri = uri;
    340         }
    341         String[] uris = context.getResources().getStringArray(
    342                 com.android.internal.R.array.config_ringtoneEffectUris);
    343         for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
    344             if (uris[i] == null) {
    345                 continue;
    346             }
    347             Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
    348             if (mappedUri == null) {
    349                 continue;
    350             }
    351             if (mappedUri.equals(uncanonicalUri)) {
    352                 return get(RINGTONES[i]);
    353             }
    354         }
    355         return null;
    356     }
    357 
    358     @Override
    359     public int describeContents() {
    360         return 0;
    361     }
    362 
    363     /** @hide */
    364     public abstract void validate();
    365 
    366     /**
    367      * Gets the estimated duration of the vibration in milliseconds.
    368      *
    369      * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
    370      * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
    371      * the length is device and potentially run-time dependent), this returns -1.
    372      *
    373      * @hide
    374      */
    375     @TestApi
    376     public abstract long getDuration();
    377 
    378     /**
    379      * Scale the amplitude with the given constraints.
    380      *
    381      * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
    382      * @hide
    383      */
    384     @TestApi
    385     protected static int scale(int amplitude, float gamma, int maxAmplitude) {
    386         float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
    387         return (int) (val * maxAmplitude);
    388     }
    389 
    390     /** @hide */
    391     @TestApi
    392     public static class OneShot extends VibrationEffect implements Parcelable {
    393         private final long mDuration;
    394         private final int mAmplitude;
    395 
    396         public OneShot(Parcel in) {
    397             mDuration = in.readLong();
    398             mAmplitude = in.readInt();
    399         }
    400 
    401         public OneShot(long milliseconds, int amplitude) {
    402             mDuration = milliseconds;
    403             mAmplitude = amplitude;
    404         }
    405 
    406         @Override
    407         public long getDuration() {
    408             return mDuration;
    409         }
    410 
    411         public int getAmplitude() {
    412             return mAmplitude;
    413         }
    414 
    415         /**
    416          * Scale the amplitude of this effect.
    417          *
    418          * @param gamma the gamma adjustment to apply
    419          * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
    420          *         MAX_AMPLITUDE
    421          * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
    422          *
    423          * @return A {@link OneShot} effect with the same timing but scaled amplitude.
    424          */
    425         public OneShot scale(float gamma, int maxAmplitude) {
    426             if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
    427                 throw new IllegalArgumentException(
    428                         "Amplitude is negative or greater than MAX_AMPLITUDE");
    429             }
    430             int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
    431             return new OneShot(mDuration, newAmplitude);
    432         }
    433 
    434         /**
    435          * Resolve default values into integer amplitude numbers.
    436          *
    437          * @param defaultAmplitude the default amplitude to apply, must be between 0 and
    438          *         MAX_AMPLITUDE
    439          * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
    440          *
    441          * @hide
    442          */
    443         public OneShot resolve(int defaultAmplitude) {
    444             if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
    445                 throw new IllegalArgumentException(
    446                         "Amplitude is negative or greater than MAX_AMPLITUDE");
    447             }
    448             if (mAmplitude == DEFAULT_AMPLITUDE) {
    449                 return new OneShot(mDuration, defaultAmplitude);
    450             }
    451             return this;
    452         }
    453 
    454         @Override
    455         public void validate() {
    456             if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
    457                 throw new IllegalArgumentException(
    458                         "amplitude must either be DEFAULT_AMPLITUDE, "
    459                         + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
    460             }
    461             if (mDuration <= 0) {
    462                 throw new IllegalArgumentException(
    463                         "duration must be positive (duration=" + mDuration + ")");
    464             }
    465         }
    466 
    467         @Override
    468         public boolean equals(Object o) {
    469             if (!(o instanceof VibrationEffect.OneShot)) {
    470                 return false;
    471             }
    472             VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
    473             return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
    474         }
    475 
    476         @Override
    477         public int hashCode() {
    478             int result = 17;
    479             result += 37 * (int) mDuration;
    480             result += 37 * mAmplitude;
    481             return result;
    482         }
    483 
    484         @Override
    485         public String toString() {
    486             return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
    487         }
    488 
    489         @Override
    490         public void writeToParcel(Parcel out, int flags) {
    491             out.writeInt(PARCEL_TOKEN_ONE_SHOT);
    492             out.writeLong(mDuration);
    493             out.writeInt(mAmplitude);
    494         }
    495 
    496         public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
    497             new Parcelable.Creator<OneShot>() {
    498                 @Override
    499                 public OneShot createFromParcel(Parcel in) {
    500                     // Skip the type token
    501                     in.readInt();
    502                     return new OneShot(in);
    503                 }
    504                 @Override
    505                 public OneShot[] newArray(int size) {
    506                     return new OneShot[size];
    507                 }
    508             };
    509     }
    510 
    511     /** @hide */
    512     @TestApi
    513     public static class Waveform extends VibrationEffect implements Parcelable {
    514         private final long[] mTimings;
    515         private final int[] mAmplitudes;
    516         private final int mRepeat;
    517 
    518         public Waveform(Parcel in) {
    519             this(in.createLongArray(), in.createIntArray(), in.readInt());
    520         }
    521 
    522         public Waveform(long[] timings, int[] amplitudes, int repeat) {
    523             mTimings = new long[timings.length];
    524             System.arraycopy(timings, 0, mTimings, 0, timings.length);
    525             mAmplitudes = new int[amplitudes.length];
    526             System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
    527             mRepeat = repeat;
    528         }
    529 
    530         public long[] getTimings() {
    531             return mTimings;
    532         }
    533 
    534         public int[] getAmplitudes() {
    535             return mAmplitudes;
    536         }
    537 
    538         public int getRepeatIndex() {
    539             return mRepeat;
    540         }
    541 
    542         @Override
    543         public long getDuration() {
    544             if (mRepeat >= 0) {
    545                 return Long.MAX_VALUE;
    546             }
    547             long duration = 0;
    548             for (long d : mTimings) {
    549                 duration += d;
    550             }
    551             return duration;
    552         }
    553 
    554         /**
    555          * Scale the Waveform with the given gamma and new max amplitude.
    556          *
    557          * @param gamma the gamma adjustment to apply
    558          * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
    559          *         MAX_AMPLITUDE
    560          * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
    561          *
    562          * @return A {@link Waveform} effect with the same timings and repeat index
    563          *         but scaled amplitude.
    564          */
    565         public Waveform scale(float gamma, int maxAmplitude) {
    566             if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
    567                 throw new IllegalArgumentException(
    568                         "Amplitude is negative or greater than MAX_AMPLITUDE");
    569             }
    570             if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
    571                 // Just return a copy of the original if there's no scaling to be done.
    572                 return new Waveform(mTimings, mAmplitudes, mRepeat);
    573             }
    574 
    575             int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
    576             for (int i = 0; i < scaledAmplitudes.length; i++) {
    577                 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
    578             }
    579             return new Waveform(mTimings, scaledAmplitudes, mRepeat);
    580         }
    581 
    582         /**
    583          * Resolve default values into integer amplitude numbers.
    584          *
    585          * @param defaultAmplitude the default amplitude to apply, must be between 0 and
    586          *         MAX_AMPLITUDE
    587          * @return A {@link Waveform} effect with same physical meaning but explicitly set
    588          *         amplitude
    589          *
    590          * @hide
    591          */
    592         public Waveform resolve(int defaultAmplitude) {
    593             if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
    594                 throw new IllegalArgumentException(
    595                         "Amplitude is negative or greater than MAX_AMPLITUDE");
    596             }
    597             int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
    598             for (int i = 0; i < resolvedAmplitudes.length; i++) {
    599                 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
    600                     resolvedAmplitudes[i] = defaultAmplitude;
    601                 }
    602             }
    603             return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
    604         }
    605 
    606         @Override
    607         public void validate() {
    608             if (mTimings.length != mAmplitudes.length) {
    609                 throw new IllegalArgumentException(
    610                         "timing and amplitude arrays must be of equal length"
    611                         + " (timings.length=" + mTimings.length
    612                         + ", amplitudes.length=" + mAmplitudes.length + ")");
    613             }
    614             if (!hasNonZeroEntry(mTimings)) {
    615                 throw new IllegalArgumentException("at least one timing must be non-zero"
    616                         + " (timings=" + Arrays.toString(mTimings) + ")");
    617             }
    618             for (long timing : mTimings) {
    619                 if (timing < 0) {
    620                     throw new IllegalArgumentException("timings must all be >= 0"
    621                             + " (timings=" + Arrays.toString(mTimings) + ")");
    622                 }
    623             }
    624             for (int amplitude : mAmplitudes) {
    625                 if (amplitude < -1 || amplitude > 255) {
    626                     throw new IllegalArgumentException(
    627                             "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
    628                             + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
    629                 }
    630             }
    631             if (mRepeat < -1 || mRepeat >= mTimings.length) {
    632                 throw new IllegalArgumentException(
    633                         "repeat index must be within the bounds of the timings array"
    634                         + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
    635             }
    636         }
    637 
    638         @Override
    639         public boolean equals(Object o) {
    640             if (!(o instanceof VibrationEffect.Waveform)) {
    641                 return false;
    642             }
    643             VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
    644             return Arrays.equals(mTimings, other.mTimings)
    645                 && Arrays.equals(mAmplitudes, other.mAmplitudes)
    646                 && mRepeat == other.mRepeat;
    647         }
    648 
    649         @Override
    650         public int hashCode() {
    651             int result = 17;
    652             result += 37 * Arrays.hashCode(mTimings);
    653             result += 37 * Arrays.hashCode(mAmplitudes);
    654             result += 37 * mRepeat;
    655             return result;
    656         }
    657 
    658         @Override
    659         public String toString() {
    660             return "Waveform{mTimings=" + Arrays.toString(mTimings)
    661                 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
    662                 + ", mRepeat=" + mRepeat
    663                 + "}";
    664         }
    665 
    666         @Override
    667         public void writeToParcel(Parcel out, int flags) {
    668             out.writeInt(PARCEL_TOKEN_WAVEFORM);
    669             out.writeLongArray(mTimings);
    670             out.writeIntArray(mAmplitudes);
    671             out.writeInt(mRepeat);
    672         }
    673 
    674         private static boolean hasNonZeroEntry(long[] vals) {
    675             for (long val : vals) {
    676                 if (val != 0) {
    677                     return true;
    678                 }
    679             }
    680             return false;
    681         }
    682 
    683 
    684         public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
    685             new Parcelable.Creator<Waveform>() {
    686                 @Override
    687                 public Waveform createFromParcel(Parcel in) {
    688                     // Skip the type token
    689                     in.readInt();
    690                     return new Waveform(in);
    691                 }
    692                 @Override
    693                 public Waveform[] newArray(int size) {
    694                     return new Waveform[size];
    695                 }
    696             };
    697     }
    698 
    699     /** @hide */
    700     @TestApi
    701     public static class Prebaked extends VibrationEffect implements Parcelable {
    702         private final int mEffectId;
    703         private final boolean mFallback;
    704 
    705         private int mEffectStrength;
    706 
    707         public Prebaked(Parcel in) {
    708             this(in.readInt(), in.readByte() != 0);
    709             mEffectStrength = in.readInt();
    710         }
    711 
    712         public Prebaked(int effectId, boolean fallback) {
    713             mEffectId = effectId;
    714             mFallback = fallback;
    715             mEffectStrength = EffectStrength.MEDIUM;
    716         }
    717 
    718         public int getId() {
    719             return mEffectId;
    720         }
    721 
    722         /**
    723          * Whether the effect should fall back to a generic pattern if there's no hardware specific
    724          * implementation of it.
    725          */
    726         public boolean shouldFallback() {
    727             return mFallback;
    728         }
    729 
    730         @Override
    731         public long getDuration() {
    732             return -1;
    733         }
    734 
    735         /**
    736          * Set the effect strength of the prebaked effect.
    737          */
    738         public void setEffectStrength(int strength) {
    739             if (!isValidEffectStrength(strength)) {
    740                 throw new IllegalArgumentException("Invalid effect strength: " + strength);
    741             }
    742             mEffectStrength = strength;
    743         }
    744 
    745         /**
    746          * Set the effect strength.
    747          */
    748         public int getEffectStrength() {
    749             return mEffectStrength;
    750         }
    751 
    752         private static boolean isValidEffectStrength(int strength) {
    753             switch (strength) {
    754                 case EffectStrength.LIGHT:
    755                 case EffectStrength.MEDIUM:
    756                 case EffectStrength.STRONG:
    757                     return true;
    758                 default:
    759                     return false;
    760             }
    761         }
    762 
    763         @Override
    764         public void validate() {
    765             switch (mEffectId) {
    766                 case EFFECT_CLICK:
    767                 case EFFECT_DOUBLE_CLICK:
    768                 case EFFECT_TICK:
    769                 case EFFECT_TEXTURE_TICK:
    770                 case EFFECT_THUD:
    771                 case EFFECT_POP:
    772                 case EFFECT_HEAVY_CLICK:
    773                     break;
    774                 default:
    775                     if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
    776                         throw new IllegalArgumentException(
    777                                 "Unknown prebaked effect type (value=" + mEffectId + ")");
    778                     }
    779             }
    780             if (!isValidEffectStrength(mEffectStrength)) {
    781                 throw new IllegalArgumentException(
    782                         "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
    783             }
    784         }
    785 
    786         @Override
    787         public boolean equals(Object o) {
    788             if (!(o instanceof VibrationEffect.Prebaked)) {
    789                 return false;
    790             }
    791             VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
    792             return mEffectId == other.mEffectId
    793                 && mFallback == other.mFallback
    794                 && mEffectStrength == other.mEffectStrength;
    795         }
    796 
    797         @Override
    798         public int hashCode() {
    799             int result = 17;
    800             result += 37 * mEffectId;
    801             result += 37 * mEffectStrength;
    802             return result;
    803         }
    804 
    805         @Override
    806         public String toString() {
    807             return "Prebaked{mEffectId=" + mEffectId
    808                 + ", mEffectStrength=" + mEffectStrength
    809                 + ", mFallback=" + mFallback
    810                 + "}";
    811         }
    812 
    813 
    814         @Override
    815         public void writeToParcel(Parcel out, int flags) {
    816             out.writeInt(PARCEL_TOKEN_EFFECT);
    817             out.writeInt(mEffectId);
    818             out.writeByte((byte) (mFallback ? 1 : 0));
    819             out.writeInt(mEffectStrength);
    820         }
    821 
    822         public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
    823             new Parcelable.Creator<Prebaked>() {
    824                 @Override
    825                 public Prebaked createFromParcel(Parcel in) {
    826                     // Skip the type token
    827                     in.readInt();
    828                     return new Prebaked(in);
    829                 }
    830                 @Override
    831                 public Prebaked[] newArray(int size) {
    832                     return new Prebaked[size];
    833                 }
    834             };
    835     }
    836 
    837     public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
    838             new Parcelable.Creator<VibrationEffect>() {
    839                 @Override
    840                 public VibrationEffect createFromParcel(Parcel in) {
    841                     int token = in.readInt();
    842                     if (token == PARCEL_TOKEN_ONE_SHOT) {
    843                         return new OneShot(in);
    844                     } else if (token == PARCEL_TOKEN_WAVEFORM) {
    845                         return new Waveform(in);
    846                     } else if (token == PARCEL_TOKEN_EFFECT) {
    847                         return new Prebaked(in);
    848                     } else {
    849                         throw new IllegalStateException(
    850                                 "Unexpected vibration event type token in parcel.");
    851                     }
    852                 }
    853                 @Override
    854                 public VibrationEffect[] newArray(int size) {
    855                     return new VibrationEffect[size];
    856                 }
    857             };
    858 }
    859