From 18dd5f0d25f1004e123dc265dc498a8bf8897af9 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Thu, 2 Jul 2009 11:31:12 -0400 Subject: [PATCH] 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. --- core/java/android/os/IHardwareService.aidl | 4 +- core/java/android/os/Vibrator.java | 7 +- .../com/android/server/HardwareService.java | 249 ++++++++++++------ .../tests/HardwareServicePermissionTest.java | 4 +- 4 files changed, 182 insertions(+), 82 deletions(-) diff --git a/core/java/android/os/IHardwareService.aidl b/core/java/android/os/IHardwareService.aidl index fb121bb5e2ee1..aebcb3c049fc6 100755 --- a/core/java/android/os/IHardwareService.aidl +++ b/core/java/android/os/IHardwareService.aidl @@ -20,9 +20,9 @@ package android.os; 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 0f75289fef182..51dcff11c2070 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -24,6 +24,7 @@ package android.os; public class Vibrator { IHardwareService mService; + private final Binder mToken = new Binder(); /** @hide */ public Vibrator() @@ -40,7 +41,7 @@ public class Vibrator public void vibrate(long milliseconds) { try { - mService.vibrate(milliseconds); + mService.vibrate(milliseconds, mToken); } catch (RemoteException e) { } } @@ -65,7 +66,7 @@ public class Vibrator // 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 class Vibrator 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 5bc9b5faf61ce..7597f851e3a6e 100755 --- a/services/java/com/android/server/HardwareService.java +++ b/services/java/com/android/server/HardwareService.java @@ -37,6 +37,9 @@ import android.os.Binder; 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 @@ public class HardwareService extends IHardwareService.Stub { static final int LIGHT_FLASH_NONE = 0; static final int LIGHT_FLASH_TIMED = 1; + private final LinkedList 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 @@ public class HardwareService extends IHardwareService.Stub { mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mWakeLock.setReferenceCounted(true); + mVibrations = new LinkedList(); + mBatteryStats = BatteryStatsService.getService(); IntentFilter filter = new IntentFilter(); @@ -78,13 +136,24 @@ public class HardwareService extends IHardwareService.Stub { 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 @@ public class HardwareService extends IHardwareService.Stub { 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 class HardwareService extends IHardwareService.Stub { } } - public void cancelVibrate() { + public void cancelVibrate(IBinder token) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.VIBRATE, "cancelVibrate"); @@ -164,7 +224,13 @@ public class HardwareService extends IHardwareService.Stub { // 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 @@ public class HardwareService extends IHardwareService.Stub { } }; - 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 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 @@ public class HardwareService extends IHardwareService.Stub { 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 @@ public class HardwareService extends IHardwareService.Stub { 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 @@ public class HardwareService extends IHardwareService.Stub { 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 719e758e96a85..aebd68cc4db71 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 class HardwareServicePermissionTest extends TestCase { */ 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 class HardwareServicePermissionTest extends TestCase { */ public void testCancelVibrate() throws RemoteException { try { - mHardwareService.cancelVibrate(); + mHardwareService.cancelVibrate(new Binder()); fail("cancelVibrate did not throw SecurityException as expected"); } catch (SecurityException e) { // expected