Improve the Vibrator service by keeping track of multiple vibration requests.

There are 2 types of vibrations: simple and repeated. Simple vibrations run for
a given length of time while repeated patterns run until canceled or the calling
process dies.

If a vibration is currently running and another request is issued, the newer
request always takes precedence unless the current vibration is a simple one and
the time left is longer than the new request.

If a repeating vibration is running and a new request overrides that vibration,
the current vibration is pushed onto a stack. Once the new vibration completes,
the previous vibration resumes. IBinder tokens are used to identify Vibration
requests which means that multiple calls to Vibrator.vibrate with the same
Vibrator object will override previous vibrations on that object.
diff --git a/core/java/android/os/IHardwareService.aidl b/core/java/android/os/IHardwareService.aidl
index fb121bb..aebcb3c 100755
--- a/core/java/android/os/IHardwareService.aidl
+++ b/core/java/android/os/IHardwareService.aidl
@@ -20,9 +20,9 @@
 interface IHardwareService
 {
     // Vibrator support
-    void vibrate(long milliseconds);
+    void vibrate(long milliseconds, IBinder token);
     void vibratePattern(in long[] pattern, int repeat, IBinder token);
-    void cancelVibrate();
+    void cancelVibrate(IBinder token);
     
     // flashlight support
     boolean getFlashlightEnabled();
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 0f75289..51dcff1 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -24,6 +24,7 @@
 public class Vibrator
 {
     IHardwareService mService;
+    private final Binder mToken = new Binder();
 
     /** @hide */
     public Vibrator()
@@ -40,7 +41,7 @@
     public void vibrate(long milliseconds)
     {
         try {
-            mService.vibrate(milliseconds);
+            mService.vibrate(milliseconds, mToken);
         } catch (RemoteException e) {
         }
     }
@@ -65,7 +66,7 @@
         // anyway
         if (repeat < pattern.length) {
             try {
-                mService.vibratePattern(pattern, repeat, new Binder());
+                mService.vibratePattern(pattern, repeat, mToken);
             } catch (RemoteException e) {
             }
         } else {
@@ -79,7 +80,7 @@
     public void cancel()
     {
         try {
-            mService.cancelVibrate();
+            mService.cancelVibrate(mToken);
         } catch (RemoteException e) {
         }
     }
diff --git a/services/java/com/android/server/HardwareService.java b/services/java/com/android/server/HardwareService.java
index 5bc9b5f..7597f85 100755
--- a/services/java/com/android/server/HardwareService.java
+++ b/services/java/com/android/server/HardwareService.java
@@ -37,6 +37,9 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import java.util.LinkedList;
+import java.util.ListIterator;
+
 public class HardwareService extends IHardwareService.Stub {
     private static final String TAG = "HardwareService";
 
@@ -50,9 +53,62 @@
     static final int LIGHT_FLASH_NONE = 0;
     static final int LIGHT_FLASH_TIMED = 1;
 
+    private final LinkedList<Vibration> mVibrations;
+    private Vibration mCurrentVibration;
+
     private boolean mAttentionLightOn;
     private boolean mPulsing;
 
+    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;
+
+        Vibration(IBinder token, long millis) {
+            this(token, millis, null, 0);
+        }
+
+        Vibration(IBinder token, long[] pattern, int repeat) {
+            this(token, 0, pattern, repeat);
+        }
+
+        private Vibration(IBinder token, long millis, long[] pattern,
+                int repeat) {
+            mToken = token;
+            mTimeout = millis;
+            mStartTime = SystemClock.uptimeMillis();
+            mPattern = pattern;
+            mRepeat = repeat;
+        }
+
+        public void binderDied() {
+            synchronized (mVibrations) {
+                mVibrations.remove(this);
+                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 ((mStartTime + mTimeout)
+                    < (SystemClock.uptimeMillis() + millis)) {
+                // If this vibration will end before the time passed in, let
+                // the new vibration play.
+                return false;
+            }
+            return true;
+        }
+    }
+
     HardwareService(Context context) {
         // Reset the hardware to a default state, in case this is a runtime
         // restart instead of a fresh boot.
@@ -66,6 +122,8 @@
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mWakeLock.setReferenceCounted(true);
 
+        mVibrations = new LinkedList<Vibration>();
+
         mBatteryStats = BatteryStatsService.getService();
         
         IntentFilter filter = new IntentFilter();
@@ -78,13 +136,24 @@
         super.finalize();
     }
 
-    public void vibrate(long milliseconds) {
+    public void vibrate(long milliseconds, IBinder token) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
         }
-        doCancelVibrate();
-        vibratorOn(milliseconds);
+        if (mCurrentVibration != null
+                && mCurrentVibration.hasLongerTimeout(milliseconds)) {
+            // Ignore this vibration since the current vibration will play for
+            // longer than milliseconds.
+            return;
+        }
+        Vibration vib = new Vibration(token, milliseconds);
+        synchronized (mVibrations) {
+            removeVibrationLocked(token);
+            doCancelVibrateLocked();
+            mCurrentVibration = vib;
+            startVibrationLocked(vib);
+        }
     }
 
     private boolean isAll0(long[] pattern) {
@@ -121,34 +190,25 @@
                 return;
             }
 
-            synchronized (this) {
-                Death death = new Death(token);
-                try {
-                    token.linkToDeath(death, 0);
-                } catch (RemoteException e) {
-                    return;
+            Vibration vib = new Vibration(token, pattern, repeat);
+            try {
+                token.linkToDeath(vib, 0);
+            } catch (RemoteException e) {
+                return;
+            }
+
+            synchronized (mVibrations) {
+                removeVibrationLocked(token);
+                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.
+                    mCurrentVibration = vib;
+                    startVibrationLocked(vib);
                 }
-
-                Thread oldThread = mThread;
-
-                if (oldThread != null) {
-                    // stop the old one
-                    synchronized (mThread) {
-                        mThread.mDone = true;
-                        mThread.notify();
-                    }
-                }
-
-                if (mDeath != null) {
-                    mToken.unlinkToDeath(mDeath, 0);
-                }
-
-                mDeath = death;
-                mToken = token;
-
-                // start the new thread
-                mThread = new VibrateThread(pattern, repeat);
-                mThread.start();
             }
         }
         finally {
@@ -156,7 +216,7 @@
         }
     }
 
-    public void cancelVibrate() {
+    public void cancelVibrate(IBinder token) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.VIBRATE,
                 "cancelVibrate");
@@ -164,7 +224,13 @@
         // so wakelock calls will succeed
         long identity = Binder.clearCallingIdentity();
         try {
-            doCancelVibrate();
+            synchronized (mVibrations) {
+                final Vibration vib = removeVibrationLocked(token);
+                if (vib == mCurrentVibration) {
+                    doCancelVibrateLocked();
+                    startNextVibrationLocked();
+                }
+            }
         }
         finally {
             Binder.restoreCallingIdentity(identity);
@@ -277,27 +343,74 @@
         }
     };
 
-    private void doCancelVibrate() {
-        synchronized (this) {
-            if (mThread != null) {
-                synchronized (mThread) {
-                    mThread.mDone = true;
-                    mThread.notify();
-                }
-                mThread = null;
+    private final Runnable mVibrationRunnable = new Runnable() {
+        public void run() {
+            synchronized (mVibrations) {
+                doCancelVibrateLocked();
+                startNextVibrationLocked();
             }
-            vibratorOff();
+        }
+    };
+
+    // Lock held on mVibrations
+    private void doCancelVibrateLocked() {
+        if (mThread != null) {
+            synchronized (mThread) {
+                mThread.mDone = true;
+                mThread.notify();
+            }
+            mThread = null;
+        }
+        vibratorOff();
+        mH.removeCallbacks(mVibrationRunnable);
+    }
+
+    // Lock held on mVibrations
+    private void startNextVibrationLocked() {
+        if (mVibrations.size() <= 0) {
+            return;
+        }
+        mCurrentVibration = mVibrations.getFirst();
+        startVibrationLocked(mCurrentVibration);
+    }
+
+    // Lock held on mVibrations
+    private void startVibrationLocked(final Vibration vib) {
+        if (vib.mTimeout != 0) {
+            vibratorOn(vib.mTimeout);
+            mH.postDelayed(mVibrationRunnable, vib.mTimeout);
+        } else {
+            // mThread better be null here. doCancelVibrate should always be
+            // called before startNextVibrationLocked or startVibrationLocked.
+            mThread = new VibrateThread(vib);
+            mThread.start();
         }
     }
 
+    // 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();
+                return vib;
+            }
+        }
+        // We might be looking for a simple vibration which is only stored in
+        // mCurrentVibration.
+        if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+            return mCurrentVibration;
+        }
+        return null;
+    }
+
     private class VibrateThread extends Thread {
-        long[] mPattern;
-        int mRepeat;
+        final Vibration mVibration;
         boolean mDone;
     
-        VibrateThread(long[] pattern, int repeat) {
-            mPattern = pattern;
-            mRepeat = repeat;
+        VibrateThread(Vibration vib) {
+            mVibration = vib;
             mWakeLock.acquire();
         }
 
@@ -323,8 +436,9 @@
             Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
             synchronized (this) {
                 int index = 0;
-                long[] pattern = mPattern;
+                long[] pattern = mVibration.mPattern;
                 int len = pattern.length;
+                int repeat = mVibration.mRepeat;
                 long duration = 0;
 
                 while (!mDone) {
@@ -347,50 +461,37 @@
                             HardwareService.this.vibratorOn(duration);
                         }
                     } else {
-                        if (mRepeat < 0) {
+                        if (repeat < 0) {
                             break;
                         } else {
-                            index = mRepeat;
+                            index = repeat;
                             duration = 0;
                         }
                     }
                 }
-                if (mDone) {
-                    // make sure vibrator is off if we were cancelled.
-                    // otherwise, it will turn off automatically 
-                    // when the last timeout expires.
-                    HardwareService.this.vibratorOff();
-                }
                 mWakeLock.release();
             }
-            synchronized (HardwareService.this) {
+            synchronized (mVibrations) {
                 if (mThread == this) {
                     mThread = null;
                 }
+                if (!mDone) {
+                    // If this vibration finished naturally, start the next
+                    // vibration.
+                    mVibrations.remove(mVibration);
+                    startNextVibrationLocked();
+                }
             }
         }
     };
 
-    private class Death implements IBinder.DeathRecipient {
-        IBinder mMe;
-
-        Death(IBinder me) {
-            mMe = me;
-        }
-
-        public void binderDied() {
-            synchronized (HardwareService.this) {
-                if (mMe == mToken) {
-                    doCancelVibrate();
-                }
-            }
-        }
-    }
-
     BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
-                doCancelVibrate();
+                synchronized (mVibrations) {
+                    doCancelVibrateLocked();
+                    mVibrations.clear();
+                }
             }
         }
     };
@@ -407,8 +508,6 @@
     private final IBatteryStats mBatteryStats;
     
     volatile VibrateThread mThread;
-    volatile Death mDeath;
-    volatile IBinder mToken;
 
     private int mNativePointer;
 
diff --git a/tests/permission/src/com/android/framework/permission/tests/HardwareServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/HardwareServicePermissionTest.java
index 719e758..aebd68c 100644
--- a/tests/permission/src/com/android/framework/permission/tests/HardwareServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/HardwareServicePermissionTest.java
@@ -46,7 +46,7 @@
      */
     public void testVibrate() throws RemoteException {
         try {
-            mHardwareService.vibrate(2000);
+            mHardwareService.vibrate(2000, new Binder());
             fail("vibrate did not throw SecurityException as expected");
         } catch (SecurityException e) {
             // expected
@@ -77,7 +77,7 @@
      */
     public void testCancelVibrate() throws RemoteException {
         try {
-            mHardwareService.cancelVibrate();
+            mHardwareService.cancelVibrate(new Binder());
             fail("cancelVibrate did not throw SecurityException as expected");
         } catch (SecurityException e) {
             // expected