BZZZZZZT! BZZZZZT! New Vibrator APIs

Replace the existing Vibrator APIs with a new class to encapsulate the
haptic effect information, and add the ability to control the vibration
strength.

Test: cts-tradefed run commandAndExit cts-dev -m CtsOsTestCases -t android.os.cts.VibratorTest
      cts-tradefed run commandAndExit cts-dev -m CtsOsTestCases -t android.os.cts.VibrationEffectTest
      runtest systemui-notification
      Manual testing with $T/google_experimental/users/michaelwr/Vibrator

Bug: 30961353

Change-Id: Idbb9864a7b084c85e7b7de3257a0e6b40d9d91d6
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5fe6952..c4676d1 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -22,8 +22,10 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.input.InputManager;
+import android.hardware.vibrator.V1_0.Constants.EffectStrength;
 import android.media.AudioManager;
 import android.os.PowerSaveState;
 import android.os.BatteryStats;
@@ -42,6 +44,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -67,12 +70,14 @@
     private static final boolean DEBUG = false;
     private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
 
-    private final LinkedList<Vibration> mVibrations;
     private final LinkedList<VibrationInfo> mPreviousVibrations;
     private final int mPreviousVibrationsLimit;
-    private Vibration mCurrentVibration;
+    private final boolean mSupportsAmplitudeControl;
+    private final int mDefaultVibrationAmplitude;
+    private final VibrationEffect[] mFallbackEffects;
     private final WorkSource mTmpWorkSource = new WorkSource();
     private final Handler mH = new Handler();
+    private final Object mLock = new Object();
 
     private final Context mContext;
     private final PowerManager.WakeLock mWakeLock;
@@ -81,14 +86,15 @@
     private PowerManagerInternal mPowerManagerInternal;
     private InputManager mIm;
 
-    volatile VibrateThread mThread;
+    private volatile VibrateThread mThread;
 
-    // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+    // mInputDeviceVibrators lock should be acquired after mLock, if both are
     // to be acquired
     private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
     private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
     private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
 
+    private Vibration mCurrentVibration;
     private int mCurVibUid = -1;
     private boolean mLowPowerMode;
     private SettingsObserver mSettingObserver;
@@ -97,106 +103,87 @@
     native static void vibratorInit();
     native static void vibratorOn(long milliseconds);
     native static void vibratorOff();
+    native static boolean vibratorSupportsAmplitudeControl();
+    native static void vibratorSetAmplitude(int amplitude);
+    native static long vibratorPerformEffect(long effect, long strength);
 
     private class Vibration implements IBinder.DeathRecipient {
         private final IBinder mToken;
-        private final long    mTimeout;
-        private final long    mStartTime;
-        private final long[]  mPattern;
-        private final int     mRepeat;
-        private final int     mUsageHint;
-        private final int     mUid;
-        private final String  mOpPkg;
+        private final VibrationEffect mEffect;
+        private final long mStartTime;
+        private final int mUsageHint;
+        private final int mUid;
+        private final String mOpPkg;
 
-        Vibration(IBinder token, long millis, int usageHint, int uid, String opPkg) {
-            this(token, millis, null, 0, usageHint, uid, opPkg);
-        }
-
-        Vibration(IBinder token, long[] pattern, int repeat, int usageHint, int uid,
-                String opPkg) {
-            this(token, 0, pattern, repeat, usageHint, uid, opPkg);
-        }
-
-        private Vibration(IBinder token, long millis, long[] pattern,
-                int repeat, int usageHint, int uid, String opPkg) {
+        private Vibration(IBinder token, VibrationEffect effect,
+                int usageHint, int uid, String opPkg) {
             mToken = token;
-            mTimeout = millis;
+            mEffect = effect;
             mStartTime = SystemClock.uptimeMillis();
-            mPattern = pattern;
-            mRepeat = repeat;
             mUsageHint = usageHint;
             mUid = uid;
             mOpPkg = opPkg;
         }
 
         public void binderDied() {
-            synchronized (mVibrations) {
-                mVibrations.remove(this);
+            synchronized (mLock) {
                 if (this == mCurrentVibration) {
                     doCancelVibrateLocked();
-                    startNextVibrationLocked();
                 }
             }
         }
 
         public boolean hasLongerTimeout(long millis) {
-            if (mTimeout == 0) {
-                // This is a pattern, return false to play the simple
-                // vibration.
-                return false;
+            // If the current effect is a one shot vibration that will end after the given timeout
+            // for the new one shot vibration, then just let the current vibration finish. All
+            // other effect types will get pre-empted.
+            if (mEffect instanceof VibrationEffect.OneShot) {
+                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) mEffect;
+                return mStartTime + oneShot.getTiming() > SystemClock.uptimeMillis() + millis;
             }
-            if ((mStartTime + mTimeout)
-                    < (SystemClock.uptimeMillis() + millis)) {
-                // If this vibration will end before the time passed in, let
-                // the new vibration play.
-                return false;
-            }
-            return true;
+            return false;
         }
 
         public boolean isSystemHapticFeedback() {
+            boolean repeating = false;
+            if (mEffect instanceof VibrationEffect.Waveform) {
+                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) mEffect;
+                repeating = (waveform.getRepeatIndex() < 0);
+            }
             return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
-                    && mRepeat < 0;
+                    && !repeating;
         }
     }
 
     private static class VibrationInfo {
-        long timeout;
-        long startTime;
-        long[] pattern;
-        int repeat;
-        int usageHint;
-        int uid;
-        String opPkg;
+        private final long mStartTime;
+        private final VibrationEffect mEffect;
+        private final int mUsageHint;
+        private final int mUid;
+        private final String mOpPkg;
 
-        public VibrationInfo(long timeout, long startTime, long[] pattern, int repeat,
+        public VibrationInfo(long startTime, VibrationEffect effect,
                 int usageHint, int uid, String opPkg) {
-            this.timeout = timeout;
-            this.startTime = startTime;
-            this.pattern = pattern;
-            this.repeat = repeat;
-            this.usageHint = usageHint;
-            this.uid = uid;
-            this.opPkg = opPkg;
+            mStartTime = startTime;
+            mEffect = effect;
+            mUsageHint = usageHint;
+            mUid = uid;
+            mOpPkg = opPkg;
         }
 
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("timeout: ")
-                    .append(timeout)
                     .append(", startTime: ")
-                    .append(startTime)
-                    .append(", pattern: ")
-                    .append(Arrays.toString(pattern))
-                    .append(", repeat: ")
-                    .append(repeat)
+                    .append(mStartTime)
+                    .append(", effect: ")
+                    .append(mEffect)
                     .append(", usageHint: ")
-                    .append(usageHint)
+                    .append(mUsageHint)
                     .append(", uid: ")
-                    .append(uid)
+                    .append(mUid)
                     .append(", opPkg: ")
-                    .append(opPkg)
+                    .append(mOpPkg)
                     .toString();
         }
     }
@@ -207,25 +194,38 @@
         // restart instead of a fresh boot.
         vibratorOff();
 
+        mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+
         mContext = context;
-        PowerManager pm = (PowerManager)context.getSystemService(
-                Context.POWER_SERVICE);
+        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
         mWakeLock.setReferenceCounted(true);
 
-        mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+        mAppOpsService =
+            IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
         mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                 BatteryStats.SERVICE_NAME));
 
         mPreviousVibrationsLimit = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_previousVibrationsDumpLimit);
 
-        mVibrations = new LinkedList<>();
+        mDefaultVibrationAmplitude = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_defaultVibrationAmplitude);
+
         mPreviousVibrations = new LinkedList<>();
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         context.registerReceiver(mIntentReceiver, filter);
+
+        long[] clickEffectTimings = getLongIntArray(context.getResources(),
+                com.android.internal.R.array.config_virtualKeyVibePattern);
+        VibrationEffect clickEffect = VibrationEffect.createWaveform(clickEffectTimings, -1);
+        VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
+                new long[] {0, 30, 100, 30} /*timings*/, -1);
+
+        mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect };
+
     }
 
     public void systemReady() {
@@ -242,7 +242,7 @@
 
                     @Override
                     public void onLowPowerModeChanged(PowerSaveState result) {
-                        updateInputDeviceVibrators();
+                        updateVibrators();
                     }
         });
 
@@ -253,11 +253,11 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                updateInputDeviceVibrators();
+                updateVibrators();
             }
         }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
 
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     private final class SettingsObserver extends ContentObserver {
@@ -267,7 +267,7 @@
 
         @Override
         public void onChange(boolean SelfChange) {
-            updateInputDeviceVibrators();
+            updateVibrators();
         }
     }
 
@@ -276,6 +276,15 @@
         return doVibratorExists();
     }
 
+    @Override // Binder call
+    public boolean hasAmplitudeControl() {
+        synchronized (mInputDeviceVibrators) {
+            // Input device vibrators don't support amplitude controls yet, but are still used over
+            // the system vibrator when connected.
+            return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
+        }
+    }
+
     private void verifyIncomingUid(int uid) {
         if (uid == Binder.getCallingUid()) {
             return;
@@ -287,103 +296,96 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
+    /**
+     * Validate the incoming VibrationEffect.
+     *
+     * We can't throw exceptions here since we might be called from some system_server component,
+     * which would bring the whole system down.
+     *
+     * @return whether the VibrationEffect is valid
+     */
+    private static boolean verifyVibrationEffect(VibrationEffect effect) {
+        if (effect == null) {
+            // Effect must not be null.
+            Slog.wtf(TAG, "effect must not be null");
+            return false;
+        }
+        try {
+            effect.validate();
+        } catch (Exception e) {
+            Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
+            return false;
+        }
+        return true;
+    }
+
+    private static long[] getLongIntArray(Resources r, int resid) {
+        int[] ar = r.getIntArray(resid);
+        if (ar == null) {
+            return null;
+        }
+        long[] out = new long[ar.length];
+        for (int i = 0; i < ar.length; i++) {
+            out[i] = ar[i];
+        }
+        return out;
+    }
+
     @Override // Binder call
-    public void vibrate(int uid, String opPkg, long milliseconds, int usageHint,
+    public void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint,
             IBinder token) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
         }
+        if (token == null) {
+            Slog.e(TAG, "token must not be null");
+            return;
+        }
         verifyIncomingUid(uid);
-        // We're running in the system server so we cannot crash. Check for a
-        // timeout of 0 or negative. This will ensure that a vibration has
-        // either a timeout of > 0 or a non-null pattern.
-        if (milliseconds <= 0 || (mCurrentVibration != null
-                && mCurrentVibration.hasLongerTimeout(milliseconds))) {
-            // Ignore this vibration since the current vibration will play for
-            // longer than milliseconds.
+        if (!verifyVibrationEffect(effect)) {
             return;
         }
 
-        if (DEBUG) {
-            Slog.d(TAG, "Vibrating for " + milliseconds + " ms.");
-        }
-
-        Vibration vib = new Vibration(token, milliseconds, usageHint, uid, opPkg);
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mVibrations) {
-                removeVibrationLocked(token);
-                doCancelVibrateLocked();
-                addToPreviousVibrationsLocked(vib);
-                startVibrationLocked(vib);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private boolean isAll0(long[] pattern) {
-        int N = pattern.length;
-        for (int i = 0; i < N; i++) {
-            if (pattern[i] != 0) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override // Binder call
-    public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
-            int usageHint, IBinder token) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires VIBRATE permission");
-        }
-        verifyIncomingUid(uid);
-        // so wakelock calls will succeed
-        long identity = Binder.clearCallingIdentity();
-        try {
-            if (DEBUG) {
-                String s = "";
-                int N = pattern.length;
-                for (int i=0; i<N; i++) {
-                    s += " " + pattern[i];
+        // If our current vibration is longer than the new vibration and is the same amplitude,
+        // then just let the current one finish.
+        if (effect instanceof VibrationEffect.OneShot
+                && mCurrentVibration != null
+                && mCurrentVibration.mEffect instanceof VibrationEffect.OneShot) {
+            VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+            VibrationEffect.OneShot currentOneShot =
+                    (VibrationEffect.OneShot) mCurrentVibration.mEffect;
+            if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
+                    && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+                if (DEBUG) {
+                    Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
                 }
-                Slog.d(TAG, "Vibrating with pattern:" + s);
-            }
-
-            // we're running in the server so we can't fail
-            if (pattern == null || pattern.length == 0
-                    || isAll0(pattern)
-                    || repeat >= pattern.length || token == null) {
                 return;
             }
+        }
 
-            Vibration vib = new Vibration(token, pattern, repeat, usageHint, uid, packageName);
+        Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
+
+        // Only link against waveforms since they potentially don't have a finish if
+        // they're repeating. Let other effects just play out until they're done.
+        if (effect instanceof VibrationEffect.Waveform) {
             try {
                 token.linkToDeath(vib, 0);
             } catch (RemoteException e) {
                 return;
             }
+        }
 
-            synchronized (mVibrations) {
-                removeVibrationLocked(token);
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
                 doCancelVibrateLocked();
-                if (repeat >= 0) {
-                    mVibrations.addFirst(vib);
-                    startNextVibrationLocked();
-                } else {
-                    // A negative repeat means that this pattern is not meant
-                    // to repeat. Treat it like a simple vibration.
-                    startVibrationLocked(vib);
-                }
+                startVibrationLocked(vib);
                 addToPreviousVibrationsLocked(vib);
             }
-        }
-        finally {
-            Binder.restoreCallingIdentity(identity);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -391,8 +393,8 @@
         if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
             mPreviousVibrations.removeFirst();
         }
-        mPreviousVibrations.addLast(new VibratorService.VibrationInfo(vib.mTimeout, vib.mStartTime,
-                vib.mPattern, vib.mRepeat, vib.mUsageHint, vib.mUid, vib.mOpPkg));
+        mPreviousVibrations.addLast(new VibrationInfo(
+                    vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg));
     }
 
     @Override // Binder call
@@ -401,97 +403,97 @@
                 android.Manifest.permission.VIBRATE,
                 "cancelVibrate");
 
-        // so wakelock calls will succeed
-        long identity = Binder.clearCallingIdentity();
-        try {
-            synchronized (mVibrations) {
-                final Vibration vib = removeVibrationLocked(token);
-                if (vib == mCurrentVibration) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Canceling vibration.");
-                    }
+        synchronized (mLock) {
+            if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Canceling vibration.");
+                }
+                long ident = Binder.clearCallingIdentity();
+                try {
                     doCancelVibrateLocked();
-                    startNextVibrationLocked();
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
                 }
             }
         }
-        finally {
-            Binder.restoreCallingIdentity(identity);
-        }
     }
 
-    private final Runnable mVibrationRunnable = new Runnable() {
+    private final Runnable mVibrationEndRunnable = new Runnable() {
         @Override
         public void run() {
-            synchronized (mVibrations) {
-                doCancelVibrateLocked();
-                startNextVibrationLocked();
-            }
+            onVibrationFinished();
         }
     };
 
-    // Lock held on mVibrations
     private void doCancelVibrateLocked() {
+        mH.removeCallbacks(mVibrationEndRunnable);
         if (mThread != null) {
-            synchronized (mThread) {
-                mThread.mDone = true;
-                mThread.notify();
-            }
+            mThread.cancel();
             mThread = null;
         }
         doVibratorOff();
-        mH.removeCallbacks(mVibrationRunnable);
         reportFinishVibrationLocked();
     }
 
-    // Lock held on mVibrations
-    private void startNextVibrationLocked() {
-        if (mVibrations.size() <= 0) {
-            reportFinishVibrationLocked();
-            mCurrentVibration = null;
-            return;
+    // Callback for whenever the current vibration has finished played out
+    public void onVibrationFinished() {
+        if (DEBUG) {
+            Slog.e(TAG, "Vibration finished, cleaning up");
         }
-        startVibrationLocked(mVibrations.getFirst());
+        synchronized (mLock) {
+            // Make sure the vibration is really done. This also reports that the vibration is
+            // finished.
+            doCancelVibrateLocked();
+        }
     }
 
-    // Lock held on mVibrations
     private void startVibrationLocked(final Vibration vib) {
-        try {
-            if (mLowPowerMode
-                    && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
-                return;
+        if (mLowPowerMode && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+            if (DEBUG) {
+                Slog.e(TAG, "Vibrate ignored, low power mode");
             }
-
-            if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
-                    !shouldVibrateForRingtone()) {
-                return;
-            }
-
-            int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
-                    vib.mUsageHint, vib.mUid, vib.mOpPkg);
-            if (mode == AppOpsManager.MODE_ALLOWED) {
-                mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
-                    AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
-            }
-            if (mode == AppOpsManager.MODE_ALLOWED) {
-                mCurrentVibration = vib;
-            } else {
-                if (mode == AppOpsManager.MODE_ERRORED) {
-                    Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
-                }
-                mH.post(mVibrationRunnable);
-                return;
-            }
-        } catch (RemoteException e) {
+            return;
         }
-        if (vib.mTimeout != 0) {
-            doVibratorOn(vib.mTimeout, vib.mUid, vib.mUsageHint);
-            mH.postDelayed(mVibrationRunnable, vib.mTimeout);
-        } else {
+
+        if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
+                !shouldVibrateForRingtone()) {
+            if (DEBUG) {
+                Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+            }
+            return;
+        }
+
+        final int mode = getAppOpMode(vib);
+        if (mode != AppOpsManager.MODE_ALLOWED) {
+            if (mode == AppOpsManager.MODE_ERRORED) {
+                // We might be getting calls from within system_server, so we don't actually want
+                // to throw a SecurityException here.
+                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
+            }
+            return;
+        }
+        startVibrationInnerLocked(vib);
+    }
+
+    private void startVibrationInnerLocked(Vibration vib) {
+        mCurrentVibration = vib;
+        if (vib.mEffect instanceof VibrationEffect.OneShot) {
+            VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.mEffect;
+            doVibratorOn(oneShot.getTiming(), oneShot.getAmplitude(), vib.mUid, vib.mUsageHint);
+            mH.postDelayed(mVibrationEndRunnable, oneShot.getTiming());
+        } else if (vib.mEffect instanceof VibrationEffect.Waveform) {
             // mThread better be null here. doCancelVibrate should always be
             // called before startNextVibrationLocked or startVibrationLocked.
-            mThread = new VibrateThread(vib);
+            VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.mEffect;
+            mThread = new VibrateThread(waveform, vib.mUid, vib.mUsageHint);
             mThread.start();
+        } else if (vib.mEffect instanceof VibrationEffect.Prebaked) {
+            long timeout = doVibratorPrebakedEffectLocked(vib);
+            if (timeout > 0) {
+                mH.postDelayed(mVibrationEndRunnable, timeout);
+            }
+        } else {
+            Slog.e(TAG, "Unknown vibration type, ignoring");
         }
     }
 
@@ -507,104 +509,115 @@
         }
     }
 
+    private int getAppOpMode(Vibration vib) {
+        int mode;
+        try {
+            mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
+                    vib.mUsageHint, vib.mUid, vib.mOpPkg);
+            if (mode == AppOpsManager.MODE_ALLOWED) {
+                mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+                    AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get appop mode for vibration!", e);
+            mode = AppOpsManager.MODE_IGNORED;
+        }
+        return mode;
+    }
+
     private void reportFinishVibrationLocked() {
         if (mCurrentVibration != null) {
             try {
                 mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
                         AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
                         mCurrentVibration.mOpPkg);
-            } catch (RemoteException e) {
-            }
+            } catch (RemoteException e) { }
             mCurrentVibration = null;
         }
     }
 
-    // Lock held on mVibrations
-    private Vibration removeVibrationLocked(IBinder token) {
-        ListIterator<Vibration> iter = mVibrations.listIterator(0);
-        while (iter.hasNext()) {
-            Vibration vib = iter.next();
-            if (vib.mToken == token) {
-                iter.remove();
-                unlinkVibration(vib);
-                return vib;
-            }
-        }
-        // We might be looking for a simple vibration which is only stored in
-        // mCurrentVibration.
-        if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
-            unlinkVibration(mCurrentVibration);
-            return mCurrentVibration;
-        }
-        return null;
-    }
-
     private void unlinkVibration(Vibration vib) {
-        if (vib.mPattern != null) {
-            // If Vibration object has a pattern,
-            // the Vibration object has also been linkedToDeath.
+        if (vib.mEffect instanceof VibrationEffect.Waveform) {
             vib.mToken.unlinkToDeath(vib, 0);
         }
     }
 
-    private void updateInputDeviceVibrators() {
-        synchronized (mVibrations) {
-            doCancelVibrateLocked();
+    private void updateVibrators() {
+        synchronized (mLock) {
+            boolean devicesUpdated = updateInputDeviceVibratorsLocked();
+            boolean lowPowerModeUpdated = updateLowPowerModeLocked();
 
-            synchronized (mInputDeviceVibrators) {
-                mVibrateInputDevicesSetting = false;
-                try {
-                    mVibrateInputDevicesSetting = Settings.System.getIntForUser(
-                            mContext.getContentResolver(),
-                            Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
-                } catch (SettingNotFoundException snfe) {
-                }
+            if (devicesUpdated || lowPowerModeUpdated) {
+                // If the state changes out from under us then just reset.
+                doCancelVibrateLocked();
+            }
+        }
+    }
 
-                mLowPowerMode = mPowerManagerInternal
-                        .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+    private boolean updateInputDeviceVibratorsLocked() {
+        boolean changed = false;
+        boolean vibrateInputDevices = false;
+        try {
+            vibrateInputDevices = Settings.System.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
+        } catch (SettingNotFoundException snfe) {
+        }
+        if (vibrateInputDevices != mVibrateInputDevicesSetting) {
+            changed = true;
+            mVibrateInputDevicesSetting = vibrateInputDevices;
+        }
 
-                if (mVibrateInputDevicesSetting) {
-                    if (!mInputDeviceListenerRegistered) {
-                        mInputDeviceListenerRegistered = true;
-                        mIm.registerInputDeviceListener(this, mH);
-                    }
-                } else {
-                    if (mInputDeviceListenerRegistered) {
-                        mInputDeviceListenerRegistered = false;
-                        mIm.unregisterInputDeviceListener(this);
-                    }
-                }
+        if (mVibrateInputDevicesSetting) {
+            if (!mInputDeviceListenerRegistered) {
+                mInputDeviceListenerRegistered = true;
+                mIm.registerInputDeviceListener(this, mH);
+            }
+        } else {
+            if (mInputDeviceListenerRegistered) {
+                mInputDeviceListenerRegistered = false;
+                mIm.unregisterInputDeviceListener(this);
+            }
+        }
 
-                mInputDeviceVibrators.clear();
-                if (mVibrateInputDevicesSetting) {
-                    int[] ids = mIm.getInputDeviceIds();
-                    for (int i = 0; i < ids.length; i++) {
-                        InputDevice device = mIm.getInputDevice(ids[i]);
-                        Vibrator vibrator = device.getVibrator();
-                        if (vibrator.hasVibrator()) {
-                            mInputDeviceVibrators.add(vibrator);
-                        }
-                    }
+        mInputDeviceVibrators.clear();
+        if (mVibrateInputDevicesSetting) {
+            int[] ids = mIm.getInputDeviceIds();
+            for (int i = 0; i < ids.length; i++) {
+                InputDevice device = mIm.getInputDevice(ids[i]);
+                Vibrator vibrator = device.getVibrator();
+                if (vibrator.hasVibrator()) {
+                    mInputDeviceVibrators.add(vibrator);
                 }
             }
-
-            startNextVibrationLocked();
+            return true;
         }
+        return changed;
+    }
+
+    private boolean updateLowPowerModeLocked() {
+        boolean lowPowerMode = mPowerManagerInternal
+                .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+        if (lowPowerMode != mLowPowerMode) {
+            mLowPowerMode = lowPowerMode;
+            return true;
+        }
+        return false;
     }
 
     @Override
     public void onInputDeviceAdded(int deviceId) {
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     @Override
     public void onInputDeviceChanged(int deviceId) {
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     @Override
     public void onInputDeviceRemoved(int deviceId) {
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     private boolean doVibratorExists() {
@@ -619,41 +632,44 @@
         return vibratorExists();
     }
 
-    private void doVibratorOn(long millis, int uid, int usageHint) {
+    private void doVibratorOn(long millis, int amplitude, int uid, int usageHint) {
         synchronized (mInputDeviceVibrators) {
+            if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
+                amplitude = mDefaultVibrationAmplitude;
+            }
             if (DEBUG) {
-                Slog.d(TAG, "Turning vibrator on for " + millis + " ms.");
+                Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
+                        " with amplitude " + amplitude + ".");
             }
-            try {
-                mBatteryStatsService.noteVibratorOn(uid, millis);
-                mCurVibUid = uid;
-            } catch (RemoteException e) {
-            }
+            noteVibratorOnLocked(uid, millis);
             final int vibratorCount = mInputDeviceVibrators.size();
             if (vibratorCount != 0) {
-                final AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usageHint)
-                        .build();
+                final AudioAttributes attributes =
+                        new AudioAttributes.Builder().setUsage(usageHint).build();
                 for (int i = 0; i < vibratorCount; i++) {
                     mInputDeviceVibrators.get(i).vibrate(millis, attributes);
                 }
             } else {
+                // Note: ordering is important here! Many haptic drivers will reset their amplitude
+                // when enabled, so we always have to enable frst, then set the amplitude.
                 vibratorOn(millis);
+                doVibratorSetAmplitude(amplitude);
             }
         }
     }
 
+    private void doVibratorSetAmplitude(int amplitude) {
+        if (mSupportsAmplitudeControl) {
+            vibratorSetAmplitude(amplitude);
+        }
+    }
+
     private void doVibratorOff() {
         synchronized (mInputDeviceVibrators) {
             if (DEBUG) {
                 Slog.d(TAG, "Turning vibrator off.");
             }
-            if (mCurVibUid >= 0) {
-                try {
-                    mBatteryStatsService.noteVibratorOff(mCurVibUid);
-                } catch (RemoteException e) {
-                }
-                mCurVibUid = -1;
-            }
+            noteVibratorOffLocked();
             final int vibratorCount = mInputDeviceVibrators.size();
             if (vibratorCount != 0) {
                 for (int i = 0; i < vibratorCount; i++) {
@@ -665,86 +681,175 @@
         }
     }
 
-    private class VibrateThread extends Thread {
-        final Vibration mVibration;
-        boolean mDone;
+    private long doVibratorPrebakedEffectLocked(Vibration vib) {
+        synchronized (mInputDeviceVibrators) {
+            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
+            // Input devices don't support prebaked effect, so skip trying it with them.
+            final int vibratorCount = mInputDeviceVibrators.size();
+            if (vibratorCount == 0) {
+                long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
+                if (timeout > 0) {
+                    noteVibratorOnLocked(vib.mUid, timeout);
+                    return timeout;
+                }
+            }
+            final int id = prebaked.getId();
+            if (id < 0 || id >= mFallbackEffects.length) {
+                Slog.w(TAG, "Failed to play prebaked effect, no fallback");
+                return 0;
+            }
+            VibrationEffect effect = mFallbackEffects[id];
+            Vibration fallbackVib =
+                    new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
+            startVibrationInnerLocked(fallbackVib);
+        }
+        return 0;
+    }
 
-        VibrateThread(Vibration vib) {
-            mVibration = vib;
-            mTmpWorkSource.set(vib.mUid);
+    private void noteVibratorOnLocked(int uid, long millis) {
+        try {
+            mBatteryStatsService.noteVibratorOn(uid, millis);
+            mCurVibUid = uid;
+        } catch (RemoteException e) {
+        }
+    }
+
+    private void noteVibratorOffLocked() {
+        if (mCurVibUid >= 0) {
+            try {
+                mBatteryStatsService.noteVibratorOff(mCurVibUid);
+            } catch (RemoteException e) { }
+            mCurVibUid = -1;
+        }
+    }
+
+    private class VibrateThread extends Thread {
+        private final VibrationEffect.Waveform mWaveform;
+        private final int mUid;
+        private final int mUsageHint;
+
+        private boolean mForceStop;
+
+        VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
+            mWaveform = waveform;
+            mUid = uid;
+            mUsageHint = usageHint;
+            mTmpWorkSource.set(uid);
             mWakeLock.setWorkSource(mTmpWorkSource);
-            mWakeLock.acquire();
         }
 
-        private void delay(long duration) {
+        private long delayLocked(long duration) {
+            long durationRemaining = duration;
             if (duration > 0) {
-                long bedtime = duration + SystemClock.uptimeMillis();
+                final long bedtime = duration + SystemClock.uptimeMillis();
                 do {
                     try {
-                        this.wait(duration);
+                        this.wait(durationRemaining);
                     }
-                    catch (InterruptedException e) {
-                    }
-                    if (mDone) {
+                    catch (InterruptedException e) { }
+                    if (mForceStop) {
                         break;
                     }
-                    duration = bedtime - SystemClock.uptimeMillis();
-                } while (duration > 0);
+                    durationRemaining = bedtime - SystemClock.uptimeMillis();
+                } while (durationRemaining > 0);
+                return duration - durationRemaining;
             }
+            return 0;
         }
 
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
-            synchronized (this) {
-                final long[] pattern = mVibration.mPattern;
-                final int len = pattern.length;
-                final int repeat = mVibration.mRepeat;
-                final int uid = mVibration.mUid;
-                final int usageHint = mVibration.mUsageHint;
-                int index = 0;
-                long duration = 0;
-
-                while (!mDone) {
-                    // add off-time duration to any accumulated on-time duration
-                    if (index < len) {
-                        duration += pattern[index++];
-                    }
-
-                    // sleep until it is time to start the vibrator
-                    delay(duration);
-                    if (mDone) {
-                        break;
-                    }
-
-                    if (index < len) {
-                        // read on-time duration and start the vibrator
-                        // duration is saved for delay() at top of loop
-                        duration = pattern[index++];
-                        if (duration > 0) {
-                            VibratorService.this.doVibratorOn(duration, uid, usageHint);
-                        }
-                    } else {
-                        if (repeat < 0) {
-                            break;
-                        } else {
-                            index = repeat;
-                            duration = 0;
-                        }
-                    }
+            mWakeLock.acquire();
+            try {
+                boolean finished = playWaveform();
+                if (finished) {
+                    onVibrationFinished();
                 }
+            } finally {
                 mWakeLock.release();
             }
-            synchronized (mVibrations) {
-                if (mThread == this) {
-                    mThread = null;
+        }
+
+        /**
+         * Play the waveform.
+         *
+         * @return true if it finished naturally, false otherwise (e.g. it was canceled).
+         */
+        public boolean playWaveform() {
+            synchronized (this) {
+                final long[] timings = mWaveform.getTimings();
+                final int[] amplitudes = mWaveform.getAmplitudes();
+                final int len = timings.length;
+                final int repeat = mWaveform.getRepeatIndex();
+
+                int index = 0;
+                long onDuration = 0;
+                while (!mForceStop) {
+                    if (index < len) {
+                        final int amplitude = amplitudes[index];
+                        final long duration = timings[index++];
+                        if (duration <= 0) {
+                            continue;
+                        }
+                        if (amplitude != 0) {
+                            if (onDuration <= 0) {
+                                // Telling the vibrator to start multiple times usually causes
+                                // effects to feel "choppy" because the motor resets at every on
+                                // command.  Instead we figure out how long our next "on" period is
+                                // going to be, tell the motor to stay on for the full duration,
+                                // and then wake up to change the amplitude at the appropriate
+                                // intervals.
+                                onDuration =
+                                        getTotalOnDuration(timings, amplitudes, index - 1, repeat);
+                                doVibratorOn(onDuration, amplitude, mUid, mUsageHint);
+                            } else {
+                                doVibratorSetAmplitude(amplitude);
+                            }
+                        }
+
+                        long waitTime = delayLocked(duration);
+                        if (amplitude != 0) {
+                            onDuration -= waitTime;
+                        }
+                    } else if (repeat < 0) {
+                        break;
+                    } else {
+                        index = repeat;
+                    }
                 }
-                if (!mDone) {
-                    // If this vibration finished naturally, start the next
-                    // vibration.
-                    unlinkVibration(mVibration);
-                    startNextVibrationLocked();
+                return !mForceStop;
+            }
+        }
+
+        public void cancel() {
+            synchronized (this) {
+                mThread.mForceStop = true;
+                mThread.notify();
+            }
+        }
+
+        /**
+         * Get the duration the vibrator will be on starting at startIndex until the next time it's
+         * off.
+         */
+        private long getTotalOnDuration(
+                long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
+            int i = startIndex;
+            long timing = 0;
+            while(amplitudes[i] != 0) {
+                timing += timings[i++];
+                if (i >= timings.length) {
+                    if (repeatIndex >= 0) {
+                        i = repeatIndex;
+                    } else {
+                        break;
+                    }
+                }
+                if (i == startIndex) {
+                    return 1000;
                 }
             }
+            return timing;
         }
     }
 
@@ -752,7 +857,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
-                synchronized (mVibrations) {
+                synchronized (mLock) {
                     // When the system is entering a non-interactive state, we want
                     // to cancel vibrations in case a misbehaving app has abandoned
                     // them.  However it may happen that the system is currently playing
@@ -762,16 +867,6 @@
                             && !mCurrentVibration.isSystemHapticFeedback()) {
                         doCancelVibrateLocked();
                     }
-
-                    // Clear all remaining vibrations.
-                    Iterator<Vibration> it = mVibrations.iterator();
-                    while (it.hasNext()) {
-                        Vibration vibration = it.next();
-                        if (vibration != mCurrentVibration) {
-                            unlinkVibration(vibration);
-                            it.remove();
-                        }
-                    }
                 }
             }
         }
@@ -788,7 +883,7 @@
             return;
         }
         pw.println("Previous vibrations:");
-        synchronized (mVibrations) {
+        synchronized (mLock) {
             for (VibrationInfo info : mPreviousVibrations) {
                 pw.print("  ");
                 pw.println(info.toString());
@@ -830,7 +925,10 @@
             if (description == null) {
                 description = "Shell command";
             }
-            vibrate(Binder.getCallingUid(), description, duration, AudioAttributes.USAGE_UNKNOWN,
+
+            VibrationEffect effect =
+                    VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
+            vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
                     mToken);
             return 0;
         }