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.hardware.vibrator.V1_1.Constants.Effect_1_1;
     20 
     21 import java.util.Arrays;
     22 
     23 /**
     24  * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
     25  *
     26  * These effects may be any number of things, from single shot vibrations to complex waveforms.
     27  */
     28 public abstract class VibrationEffect implements Parcelable {
     29     private static final int PARCEL_TOKEN_ONE_SHOT = 1;
     30     private static final int PARCEL_TOKEN_WAVEFORM = 2;
     31     private static final int PARCEL_TOKEN_EFFECT = 3;
     32 
     33     /**
     34      * The default vibration strength of the device.
     35      */
     36     public static final int DEFAULT_AMPLITUDE = -1;
     37 
     38     /**
     39      * A click effect.
     40      *
     41      * @see #get(int)
     42      * @hide
     43      */
     44     public static final int EFFECT_CLICK = Effect_1_1.CLICK;
     45 
     46     /**
     47      * A double click effect.
     48      *
     49      * @see #get(int)
     50      * @hide
     51      */
     52     public static final int EFFECT_DOUBLE_CLICK = Effect_1_1.DOUBLE_CLICK;
     53 
     54     /**
     55      * A tick effect.
     56      * @see #get(int)
     57      * @hide
     58      */
     59     public static final int EFFECT_TICK = Effect_1_1.TICK;
     60 
     61     /** @hide to prevent subclassing from outside of the framework */
     62     public VibrationEffect() { }
     63 
     64     /**
     65      * Create a one shot vibration.
     66      *
     67      * One shot vibrations will vibrate constantly for the specified period of time at the
     68      * specified amplitude, and then stop.
     69      *
     70      * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
     71      * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
     72      * {@link #DEFAULT_AMPLITUDE}.
     73      *
     74      * @return The desired effect.
     75      */
     76     public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
     77         VibrationEffect effect = new OneShot(milliseconds, amplitude);
     78         effect.validate();
     79         return effect;
     80     }
     81 
     82     /**
     83      * Create a waveform vibration.
     84      *
     85      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
     86      * each pair, the value in the amplitude array determines the strength of the vibration and the
     87      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
     88      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
     89      * <p>
     90      * The amplitude array of the generated waveform will be the same size as the given
     91      * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
     92      * starting with 0. Therefore the first timing value will be the period to wait before turning
     93      * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
     94      * strength, etc.
     95      * </p><p>
     96      * To cause the pattern to repeat, pass the index into the timings array at which to start the
     97      * repetition, or -1 to disable repeating.
     98      * </p>
     99      *
    100      * @param timings The pattern of alternating on-off timings, starting with off. Timing values
    101      *                of 0 will cause the timing / amplitude pair to be ignored.
    102      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
    103      *               want to repeat.
    104      *
    105      * @return The desired effect.
    106      */
    107     public static VibrationEffect createWaveform(long[] timings, int repeat) {
    108         int[] amplitudes = new int[timings.length];
    109         for (int i = 0; i < (timings.length / 2); i++) {
    110             amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
    111         }
    112         return createWaveform(timings, amplitudes, repeat);
    113     }
    114 
    115     /**
    116      * Create a waveform vibration.
    117      *
    118      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
    119      * each pair, the value in the amplitude array determines the strength of the vibration and the
    120      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
    121      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
    122      * </p><p>
    123      * To cause the pattern to repeat, pass the index into the timings array at which to start the
    124      * repetition, or -1 to disable repeating.
    125      * </p>
    126      *
    127      * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
    128      *                will cause the pair to be ignored.
    129      * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
    130      *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
    131      *                   amplitude value of 0 implies the motor is off.
    132      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
    133      *               want to repeat.
    134      *
    135      * @return The desired effect.
    136      */
    137     public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
    138         VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
    139         effect.validate();
    140         return effect;
    141     }
    142 
    143     /**
    144      * Get a predefined vibration effect.
    145      *
    146      * Predefined effects are a set of common vibration effects that should be identical, regardless
    147      * of the app they come from, in order to provide a cohesive experience for users across
    148      * the entire device. They also may be custom tailored to the device hardware in order to
    149      * provide a better experience than you could otherwise build using the generic building
    150      * blocks.
    151      *
    152      * This will fallback to a generic pattern if one exists and there does not exist a
    153      * hardware-specific implementation of the effect.
    154      *
    155      * @param effectId The ID of the effect to perform:
    156      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
    157      *
    158      * @return The desired effect.
    159      * @hide
    160      */
    161     public static VibrationEffect get(int effectId) {
    162         return get(effectId, true);
    163     }
    164 
    165     /**
    166      * Get a predefined vibration effect.
    167      *
    168      * Predefined effects are a set of common vibration effects that should be identical, regardless
    169      * of the app they come from, in order to provide a cohesive experience for users across
    170      * the entire device. They also may be custom tailored to the device hardware in order to
    171      * provide a better experience than you could otherwise build using the generic building
    172      * blocks.
    173      *
    174      * Some effects you may only want to play if there's a hardware specific implementation because
    175      * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
    176      * parameter allows you to decide whether you want to fallback to the generic implementation or
    177      * only play if there's a tuned, hardware specific one available.
    178      *
    179      * @param effectId The ID of the effect to perform:
    180      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
    181      * @param fallback Whether to fallback to a generic pattern if a hardware specific
    182      *                 implementation doesn't exist.
    183      *
    184      * @return The desired effect.
    185      * @hide
    186      */
    187     public static VibrationEffect get(int effectId, boolean fallback) {
    188         VibrationEffect effect = new Prebaked(effectId, fallback);
    189         effect.validate();
    190         return effect;
    191     }
    192 
    193     @Override
    194     public int describeContents() {
    195         return 0;
    196     }
    197 
    198     /** @hide */
    199     public abstract void validate();
    200 
    201     /** @hide */
    202     public static class OneShot extends VibrationEffect implements Parcelable {
    203         private long mTiming;
    204         private int mAmplitude;
    205 
    206         public OneShot(Parcel in) {
    207             this(in.readLong(), in.readInt());
    208         }
    209 
    210         public OneShot(long milliseconds, int amplitude) {
    211             mTiming = milliseconds;
    212             mAmplitude = amplitude;
    213         }
    214 
    215         public long getTiming() {
    216             return mTiming;
    217         }
    218 
    219         public int getAmplitude() {
    220             return mAmplitude;
    221         }
    222 
    223         @Override
    224         public void validate() {
    225             if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
    226                 throw new IllegalArgumentException(
    227                         "amplitude must either be DEFAULT_AMPLITUDE, " +
    228                         "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
    229             }
    230             if (mTiming <= 0) {
    231                 throw new IllegalArgumentException(
    232                         "timing must be positive (timing=" + mTiming + ")");
    233             }
    234         }
    235 
    236         @Override
    237         public boolean equals(Object o) {
    238             if (!(o instanceof VibrationEffect.OneShot)) {
    239                 return false;
    240             }
    241             VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
    242             return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
    243         }
    244 
    245         @Override
    246         public int hashCode() {
    247             int result = 17;
    248             result = 37 * (int) mTiming;
    249             result = 37 * mAmplitude;
    250             return result;
    251         }
    252 
    253         @Override
    254         public String toString() {
    255             return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
    256         }
    257 
    258         @Override
    259         public void writeToParcel(Parcel out, int flags) {
    260             out.writeInt(PARCEL_TOKEN_ONE_SHOT);
    261             out.writeLong(mTiming);
    262             out.writeInt(mAmplitude);
    263         }
    264 
    265         public static final Parcelable.Creator<OneShot> CREATOR =
    266             new Parcelable.Creator<OneShot>() {
    267                 @Override
    268                 public OneShot createFromParcel(Parcel in) {
    269                     // Skip the type token
    270                     in.readInt();
    271                     return new OneShot(in);
    272                 }
    273                 @Override
    274                 public OneShot[] newArray(int size) {
    275                     return new OneShot[size];
    276                 }
    277             };
    278     }
    279 
    280     /** @hide */
    281     public static class Waveform extends VibrationEffect implements Parcelable {
    282         private long[] mTimings;
    283         private int[] mAmplitudes;
    284         private int mRepeat;
    285 
    286         public Waveform(Parcel in) {
    287             this(in.createLongArray(), in.createIntArray(), in.readInt());
    288         }
    289 
    290         public Waveform(long[] timings, int[] amplitudes, int repeat) {
    291             mTimings = new long[timings.length];
    292             System.arraycopy(timings, 0, mTimings, 0, timings.length);
    293             mAmplitudes = new int[amplitudes.length];
    294             System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
    295             mRepeat = repeat;
    296         }
    297 
    298         public long[] getTimings() {
    299             return mTimings;
    300         }
    301 
    302         public int[] getAmplitudes() {
    303             return mAmplitudes;
    304         }
    305 
    306         public int getRepeatIndex() {
    307             return mRepeat;
    308         }
    309 
    310         @Override
    311         public void validate() {
    312             if (mTimings.length != mAmplitudes.length) {
    313                 throw new IllegalArgumentException(
    314                         "timing and amplitude arrays must be of equal length" +
    315                         " (timings.length=" + mTimings.length +
    316                         ", amplitudes.length=" + mAmplitudes.length + ")");
    317             }
    318             if (!hasNonZeroEntry(mTimings)) {
    319                 throw new IllegalArgumentException("at least one timing must be non-zero" +
    320                         " (timings=" + Arrays.toString(mTimings) + ")");
    321             }
    322             for (long timing : mTimings) {
    323                 if (timing < 0) {
    324                     throw new IllegalArgumentException("timings must all be >= 0" +
    325                             " (timings=" + Arrays.toString(mTimings) + ")");
    326                 }
    327             }
    328             for (int amplitude : mAmplitudes) {
    329                 if (amplitude < -1 || amplitude > 255) {
    330                     throw new IllegalArgumentException(
    331                             "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" +
    332                             " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
    333                 }
    334             }
    335             if (mRepeat < -1 || mRepeat >= mTimings.length) {
    336                 throw new IllegalArgumentException(
    337                         "repeat index must be within the bounds of the timings array" +
    338                         " (timings.length=" + mTimings.length + ", index=" + mRepeat +")");
    339             }
    340         }
    341 
    342         @Override
    343         public boolean equals(Object o) {
    344             if (!(o instanceof VibrationEffect.Waveform)) {
    345                 return false;
    346             }
    347             VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
    348             return Arrays.equals(mTimings, other.mTimings) &&
    349                 Arrays.equals(mAmplitudes, other.mAmplitudes) &&
    350                 mRepeat == other.mRepeat;
    351         }
    352 
    353         @Override
    354         public int hashCode() {
    355             int result = 17;
    356             result = 37 * Arrays.hashCode(mTimings);
    357             result = 37 * Arrays.hashCode(mAmplitudes);
    358             result = 37 * mRepeat;
    359             return result;
    360         }
    361 
    362         @Override
    363         public String toString() {
    364             return "Waveform{mTimings=" + Arrays.toString(mTimings) +
    365                 ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
    366                 ", mRepeat=" + mRepeat +
    367                 "}";
    368         }
    369 
    370         @Override
    371         public void writeToParcel(Parcel out, int flags) {
    372             out.writeInt(PARCEL_TOKEN_WAVEFORM);
    373             out.writeLongArray(mTimings);
    374             out.writeIntArray(mAmplitudes);
    375             out.writeInt(mRepeat);
    376         }
    377 
    378         private static boolean hasNonZeroEntry(long[] vals) {
    379             for (long val : vals) {
    380                 if (val != 0) {
    381                     return true;
    382                 }
    383             }
    384             return false;
    385         }
    386 
    387 
    388         public static final Parcelable.Creator<Waveform> CREATOR =
    389             new Parcelable.Creator<Waveform>() {
    390                 @Override
    391                 public Waveform createFromParcel(Parcel in) {
    392                     // Skip the type token
    393                     in.readInt();
    394                     return new Waveform(in);
    395                 }
    396                 @Override
    397                 public Waveform[] newArray(int size) {
    398                     return new Waveform[size];
    399                 }
    400             };
    401     }
    402 
    403     /** @hide */
    404     public static class Prebaked extends VibrationEffect implements Parcelable {
    405         private int mEffectId;
    406         private boolean mFallback;
    407 
    408         public Prebaked(Parcel in) {
    409             this(in.readInt(), in.readByte() != 0);
    410         }
    411 
    412         public Prebaked(int effectId, boolean fallback) {
    413             mEffectId = effectId;
    414             mFallback = fallback;
    415         }
    416 
    417         public int getId() {
    418             return mEffectId;
    419         }
    420 
    421         /**
    422          * Whether the effect should fall back to a generic pattern if there's no hardware specific
    423          * implementation of it.
    424          */
    425         public boolean shouldFallback() {
    426             return mFallback;
    427         }
    428 
    429         @Override
    430         public void validate() {
    431             switch (mEffectId) {
    432                 case EFFECT_CLICK:
    433                 case EFFECT_DOUBLE_CLICK:
    434                 case EFFECT_TICK:
    435                     break;
    436                 default:
    437                     throw new IllegalArgumentException(
    438                             "Unknown prebaked effect type (value=" + mEffectId + ")");
    439             }
    440         }
    441 
    442         @Override
    443         public boolean equals(Object o) {
    444             if (!(o instanceof VibrationEffect.Prebaked)) {
    445                 return false;
    446             }
    447             VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
    448             return mEffectId == other.mEffectId && mFallback == other.mFallback;
    449         }
    450 
    451         @Override
    452         public int hashCode() {
    453             return mEffectId;
    454         }
    455 
    456         @Override
    457         public String toString() {
    458             return "Prebaked{mEffectId=" + mEffectId + ", mFallback=" + mFallback + "}";
    459         }
    460 
    461 
    462         @Override
    463         public void writeToParcel(Parcel out, int flags) {
    464             out.writeInt(PARCEL_TOKEN_EFFECT);
    465             out.writeInt(mEffectId);
    466             out.writeByte((byte) (mFallback ? 1 : 0));
    467         }
    468 
    469         public static final Parcelable.Creator<Prebaked> CREATOR =
    470             new Parcelable.Creator<Prebaked>() {
    471                 @Override
    472                 public Prebaked createFromParcel(Parcel in) {
    473                     // Skip the type token
    474                     in.readInt();
    475                     return new Prebaked(in);
    476                 }
    477                 @Override
    478                 public Prebaked[] newArray(int size) {
    479                     return new Prebaked[size];
    480                 }
    481             };
    482     }
    483 
    484     public static final Parcelable.Creator<VibrationEffect> CREATOR =
    485             new Parcelable.Creator<VibrationEffect>() {
    486                 @Override
    487                 public VibrationEffect createFromParcel(Parcel in) {
    488                     int token = in.readInt();
    489                     if (token == PARCEL_TOKEN_ONE_SHOT) {
    490                         return new OneShot(in);
    491                     } else if (token == PARCEL_TOKEN_WAVEFORM) {
    492                         return new Waveform(in);
    493                     } else if (token == PARCEL_TOKEN_EFFECT) {
    494                         return new Prebaked(in);
    495                     } else {
    496                         throw new IllegalStateException(
    497                                 "Unexpected vibration event type token in parcel.");
    498                     }
    499                 }
    500                 @Override
    501                 public VibrationEffect[] newArray(int size) {
    502                     return new VibrationEffect[size];
    503                 }
    504             };
    505 }
    506