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