blob: 72fde1123809e75a4c5695bf24db3aa2150eff38 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 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
17package com.android.server;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.pm.PackageManager;
Jeff Brown7f6c2312012-04-13 20:38:38 -070024import android.database.ContentObserver;
25import android.hardware.input.InputManager;
Joe Onorato95e4f702009-03-24 19:29:09 -070026import android.os.Handler;
Mike Lockwood3a322132009-11-24 00:30:52 -050027import android.os.IVibratorService;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.os.PowerManager;
29import android.os.Process;
30import android.os.RemoteException;
31import android.os.IBinder;
32import android.os.Binder;
33import android.os.SystemClock;
Jeff Brown7f6c2312012-04-13 20:38:38 -070034import android.os.Vibrator;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070035import android.os.WorkSource;
Jeff Brown7f6c2312012-04-13 20:38:38 -070036import android.provider.Settings;
37import android.provider.Settings.SettingNotFoundException;
Joe Onorato8a9b2202010-02-26 18:56:32 -080038import android.util.Slog;
Jeff Brown7f6c2312012-04-13 20:38:38 -070039import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
Jeff Brown7f6c2312012-04-13 20:38:38 -070041import java.util.ArrayList;
Patrick Scott18dd5f02009-07-02 11:31:12 -040042import java.util.LinkedList;
43import java.util.ListIterator;
44
Jeff Brown7f6c2312012-04-13 20:38:38 -070045public class VibratorService extends IVibratorService.Stub
46 implements InputManager.InputDeviceListener {
Mike Lockwood3a322132009-11-24 00:30:52 -050047 private static final String TAG = "VibratorService";
Mike Lockwoodcc9a63d2009-11-10 07:50:28 -050048
Patrick Scott18dd5f02009-07-02 11:31:12 -040049 private final LinkedList<Vibration> mVibrations;
50 private Vibration mCurrentVibration;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070051 private final WorkSource mTmpWorkSource = new WorkSource();
Jeff Brown7f6c2312012-04-13 20:38:38 -070052 private final Handler mH = new Handler();
53
54 private final Context mContext;
55 private final PowerManager.WakeLock mWakeLock;
56 private InputManager mIm;
57
58 volatile VibrateThread mThread;
59
60 // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
61 // to be acquired
62 private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
63 private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
64 private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
65
66 native static boolean vibratorExists();
67 native static void vibratorOn(long milliseconds);
68 native static void vibratorOff();
Patrick Scott18dd5f02009-07-02 11:31:12 -040069
Patrick Scott18dd5f02009-07-02 11:31:12 -040070 private class Vibration implements IBinder.DeathRecipient {
71 private final IBinder mToken;
72 private final long mTimeout;
73 private final long mStartTime;
74 private final long[] mPattern;
75 private final int mRepeat;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070076 private final int mUid;
Patrick Scott18dd5f02009-07-02 11:31:12 -040077
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070078 Vibration(IBinder token, long millis, int uid) {
79 this(token, millis, null, 0, uid);
Patrick Scott18dd5f02009-07-02 11:31:12 -040080 }
81
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070082 Vibration(IBinder token, long[] pattern, int repeat, int uid) {
83 this(token, 0, pattern, repeat, uid);
Patrick Scott18dd5f02009-07-02 11:31:12 -040084 }
85
86 private Vibration(IBinder token, long millis, long[] pattern,
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070087 int repeat, int uid) {
Patrick Scott18dd5f02009-07-02 11:31:12 -040088 mToken = token;
89 mTimeout = millis;
90 mStartTime = SystemClock.uptimeMillis();
91 mPattern = pattern;
92 mRepeat = repeat;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070093 mUid = uid;
Patrick Scott18dd5f02009-07-02 11:31:12 -040094 }
95
96 public void binderDied() {
97 synchronized (mVibrations) {
98 mVibrations.remove(this);
99 if (this == mCurrentVibration) {
100 doCancelVibrateLocked();
101 startNextVibrationLocked();
102 }
103 }
104 }
105
106 public boolean hasLongerTimeout(long millis) {
107 if (mTimeout == 0) {
108 // This is a pattern, return false to play the simple
109 // vibration.
110 return false;
111 }
112 if ((mStartTime + mTimeout)
113 < (SystemClock.uptimeMillis() + millis)) {
114 // If this vibration will end before the time passed in, let
115 // the new vibration play.
116 return false;
117 }
118 return true;
119 }
120 }
121
Mike Lockwood3a322132009-11-24 00:30:52 -0500122 VibratorService(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 // Reset the hardware to a default state, in case this is a runtime
124 // restart instead of a fresh boot.
125 vibratorOff();
126
127 mContext = context;
128 PowerManager pm = (PowerManager)context.getSystemService(
129 Context.POWER_SERVICE);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700130 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 mWakeLock.setReferenceCounted(true);
132
Patrick Scott18dd5f02009-07-02 11:31:12 -0400133 mVibrations = new LinkedList<Vibration>();
134
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135 IntentFilter filter = new IntentFilter();
136 filter.addAction(Intent.ACTION_SCREEN_OFF);
137 context.registerReceiver(mIntentReceiver, filter);
138 }
139
Jeff Brown7f6c2312012-04-13 20:38:38 -0700140 public void systemReady() {
141 mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);
142 mContext.getContentResolver().registerContentObserver(
143 Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES), true,
144 new ContentObserver(mH) {
145 @Override
146 public void onChange(boolean selfChange) {
Jeff Brown82065252012-04-16 13:19:05 -0700147 updateInputDeviceVibrators();
Jeff Brown7f6c2312012-04-13 20:38:38 -0700148 }
149 });
Jeff Brown82065252012-04-16 13:19:05 -0700150 updateInputDeviceVibrators();
Dianne Hackbornea9020e2010-11-04 11:39:12 -0700151 }
Jeff Brown7f6c2312012-04-13 20:38:38 -0700152
153 public boolean hasVibrator() {
154 return doVibratorExists();
155 }
156
Patrick Scott18dd5f02009-07-02 11:31:12 -0400157 public void vibrate(long milliseconds, IBinder token) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700158 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
159 != PackageManager.PERMISSION_GRANTED) {
160 throw new SecurityException("Requires VIBRATE permission");
161 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700162 int uid = Binder.getCallingUid();
Patrick Scott24f10762009-08-19 09:03:56 -0400163 // We're running in the system server so we cannot crash. Check for a
164 // timeout of 0 or negative. This will ensure that a vibration has
165 // either a timeout of > 0 or a non-null pattern.
166 if (milliseconds <= 0 || (mCurrentVibration != null
167 && mCurrentVibration.hasLongerTimeout(milliseconds))) {
Patrick Scott18dd5f02009-07-02 11:31:12 -0400168 // Ignore this vibration since the current vibration will play for
169 // longer than milliseconds.
170 return;
171 }
Jeff Brown7f6c2312012-04-13 20:38:38 -0700172
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700173 Vibration vib = new Vibration(token, milliseconds, uid);
Patrick Scott18dd5f02009-07-02 11:31:12 -0400174 synchronized (mVibrations) {
175 removeVibrationLocked(token);
176 doCancelVibrateLocked();
177 mCurrentVibration = vib;
178 startVibrationLocked(vib);
179 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
181
182 private boolean isAll0(long[] pattern) {
183 int N = pattern.length;
184 for (int i = 0; i < N; i++) {
185 if (pattern[i] != 0) {
186 return false;
187 }
188 }
189 return true;
190 }
191
192 public void vibratePattern(long[] pattern, int repeat, IBinder token) {
193 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
194 != PackageManager.PERMISSION_GRANTED) {
195 throw new SecurityException("Requires VIBRATE permission");
196 }
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700197 int uid = Binder.getCallingUid();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198 // so wakelock calls will succeed
199 long identity = Binder.clearCallingIdentity();
200 try {
201 if (false) {
202 String s = "";
203 int N = pattern.length;
204 for (int i=0; i<N; i++) {
205 s += " " + pattern[i];
206 }
Joe Onorato8a9b2202010-02-26 18:56:32 -0800207 Slog.i(TAG, "vibrating with pattern: " + s);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 }
209
210 // we're running in the server so we can't fail
211 if (pattern == null || pattern.length == 0
212 || isAll0(pattern)
213 || repeat >= pattern.length || token == null) {
214 return;
215 }
216
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700217 Vibration vib = new Vibration(token, pattern, repeat, uid);
Patrick Scott18dd5f02009-07-02 11:31:12 -0400218 try {
219 token.linkToDeath(vib, 0);
220 } catch (RemoteException e) {
221 return;
222 }
223
224 synchronized (mVibrations) {
225 removeVibrationLocked(token);
226 doCancelVibrateLocked();
227 if (repeat >= 0) {
228 mVibrations.addFirst(vib);
229 startNextVibrationLocked();
230 } else {
231 // A negative repeat means that this pattern is not meant
232 // to repeat. Treat it like a simple vibration.
233 mCurrentVibration = vib;
234 startVibrationLocked(vib);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236 }
237 }
238 finally {
239 Binder.restoreCallingIdentity(identity);
240 }
241 }
242
Patrick Scott18dd5f02009-07-02 11:31:12 -0400243 public void cancelVibrate(IBinder token) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 mContext.enforceCallingOrSelfPermission(
245 android.Manifest.permission.VIBRATE,
246 "cancelVibrate");
247
248 // so wakelock calls will succeed
249 long identity = Binder.clearCallingIdentity();
250 try {
Patrick Scott18dd5f02009-07-02 11:31:12 -0400251 synchronized (mVibrations) {
252 final Vibration vib = removeVibrationLocked(token);
253 if (vib == mCurrentVibration) {
254 doCancelVibrateLocked();
255 startNextVibrationLocked();
256 }
257 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 }
259 finally {
260 Binder.restoreCallingIdentity(identity);
261 }
262 }
Eric Olsenf42f15c2009-10-29 16:42:03 -0700263
Patrick Scott18dd5f02009-07-02 11:31:12 -0400264 private final Runnable mVibrationRunnable = new Runnable() {
265 public void run() {
266 synchronized (mVibrations) {
267 doCancelVibrateLocked();
268 startNextVibrationLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 }
Patrick Scott18dd5f02009-07-02 11:31:12 -0400270 }
271 };
272
273 // Lock held on mVibrations
274 private void doCancelVibrateLocked() {
275 if (mThread != null) {
276 synchronized (mThread) {
277 mThread.mDone = true;
278 mThread.notify();
279 }
280 mThread = null;
281 }
Jeff Brown7f6c2312012-04-13 20:38:38 -0700282 doVibratorOff();
Patrick Scott18dd5f02009-07-02 11:31:12 -0400283 mH.removeCallbacks(mVibrationRunnable);
284 }
285
286 // Lock held on mVibrations
287 private void startNextVibrationLocked() {
288 if (mVibrations.size() <= 0) {
Mathias Jeppssonb23949b2010-09-28 14:45:23 +0200289 mCurrentVibration = null;
Patrick Scott18dd5f02009-07-02 11:31:12 -0400290 return;
291 }
292 mCurrentVibration = mVibrations.getFirst();
293 startVibrationLocked(mCurrentVibration);
294 }
295
296 // Lock held on mVibrations
297 private void startVibrationLocked(final Vibration vib) {
298 if (vib.mTimeout != 0) {
Jeff Brown7f6c2312012-04-13 20:38:38 -0700299 doVibratorOn(vib.mTimeout);
Patrick Scott18dd5f02009-07-02 11:31:12 -0400300 mH.postDelayed(mVibrationRunnable, vib.mTimeout);
301 } else {
302 // mThread better be null here. doCancelVibrate should always be
303 // called before startNextVibrationLocked or startVibrationLocked.
304 mThread = new VibrateThread(vib);
305 mThread.start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 }
307 }
308
Patrick Scott18dd5f02009-07-02 11:31:12 -0400309 // Lock held on mVibrations
310 private Vibration removeVibrationLocked(IBinder token) {
311 ListIterator<Vibration> iter = mVibrations.listIterator(0);
312 while (iter.hasNext()) {
313 Vibration vib = iter.next();
314 if (vib.mToken == token) {
315 iter.remove();
Mathias Jeppssonb23949b2010-09-28 14:45:23 +0200316 unlinkVibration(vib);
Patrick Scott18dd5f02009-07-02 11:31:12 -0400317 return vib;
318 }
319 }
320 // We might be looking for a simple vibration which is only stored in
321 // mCurrentVibration.
322 if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
Mathias Jeppssonb23949b2010-09-28 14:45:23 +0200323 unlinkVibration(mCurrentVibration);
Patrick Scott18dd5f02009-07-02 11:31:12 -0400324 return mCurrentVibration;
325 }
326 return null;
327 }
328
Mathias Jeppssonb23949b2010-09-28 14:45:23 +0200329 private void unlinkVibration(Vibration vib) {
330 if (vib.mPattern != null) {
331 // If Vibration object has a pattern,
332 // the Vibration object has also been linkedToDeath.
333 vib.mToken.unlinkToDeath(vib, 0);
334 }
335 }
336
Jeff Brown7f6c2312012-04-13 20:38:38 -0700337 private void updateInputDeviceVibrators() {
338 synchronized (mVibrations) {
339 doCancelVibrateLocked();
340
341 synchronized (mInputDeviceVibrators) {
Jeff Brown82065252012-04-16 13:19:05 -0700342 mVibrateInputDevicesSetting = false;
343 try {
344 mVibrateInputDevicesSetting = Settings.System.getInt(mContext.getContentResolver(),
345 Settings.System.VIBRATE_INPUT_DEVICES) > 0;
346 } catch (SettingNotFoundException snfe) {
347 }
348
349 if (mVibrateInputDevicesSetting) {
350 if (!mInputDeviceListenerRegistered) {
351 mInputDeviceListenerRegistered = true;
352 mIm.registerInputDeviceListener(this, mH);
353 }
354 } else {
355 if (mInputDeviceListenerRegistered) {
356 mInputDeviceListenerRegistered = false;
357 mIm.unregisterInputDeviceListener(this);
358 }
359 }
360
Jeff Brown7f6c2312012-04-13 20:38:38 -0700361 mInputDeviceVibrators.clear();
362 if (mVibrateInputDevicesSetting) {
363 int[] ids = mIm.getInputDeviceIds();
364 for (int i = 0; i < ids.length; i++) {
365 InputDevice device = mIm.getInputDevice(ids[i]);
366 Vibrator vibrator = device.getVibrator();
367 if (vibrator.hasVibrator()) {
368 mInputDeviceVibrators.add(vibrator);
369 }
370 }
371 }
372 }
373
374 startNextVibrationLocked();
375 }
376 }
377
378 @Override
379 public void onInputDeviceAdded(int deviceId) {
380 updateInputDeviceVibrators();
381 }
382
383 @Override
384 public void onInputDeviceChanged(int deviceId) {
385 updateInputDeviceVibrators();
386 }
387
388 @Override
389 public void onInputDeviceRemoved(int deviceId) {
390 updateInputDeviceVibrators();
391 }
392
393 private boolean doVibratorExists() {
Jeff Brown1064a502012-05-02 16:51:37 -0700394 // For now, we choose to ignore the presence of input devices that have vibrators
395 // when reporting whether the device has a vibrator. Applications often use this
396 // information to decide whether to enable certain features so they expect the
397 // result of hasVibrator() to be constant. For now, just report whether
398 // the device has a built-in vibrator.
399 //synchronized (mInputDeviceVibrators) {
400 // return !mInputDeviceVibrators.isEmpty() || vibratorExists();
401 //}
402 return vibratorExists();
Jeff Brown7f6c2312012-04-13 20:38:38 -0700403 }
404
405 private void doVibratorOn(long millis) {
406 synchronized (mInputDeviceVibrators) {
407 final int vibratorCount = mInputDeviceVibrators.size();
408 if (vibratorCount != 0) {
409 for (int i = 0; i < vibratorCount; i++) {
410 mInputDeviceVibrators.get(i).vibrate(millis);
411 }
412 } else {
413 vibratorOn(millis);
414 }
415 }
416 }
417
418 private void doVibratorOff() {
419 synchronized (mInputDeviceVibrators) {
420 final int vibratorCount = mInputDeviceVibrators.size();
421 if (vibratorCount != 0) {
422 for (int i = 0; i < vibratorCount; i++) {
423 mInputDeviceVibrators.get(i).cancel();
424 }
425 } else {
426 vibratorOff();
427 }
428 }
429 }
430
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 private class VibrateThread extends Thread {
Patrick Scott18dd5f02009-07-02 11:31:12 -0400432 final Vibration mVibration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 boolean mDone;
Eric Olsenf42f15c2009-10-29 16:42:03 -0700434
Patrick Scott18dd5f02009-07-02 11:31:12 -0400435 VibrateThread(Vibration vib) {
436 mVibration = vib;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700437 mTmpWorkSource.set(vib.mUid);
438 mWakeLock.setWorkSource(mTmpWorkSource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 mWakeLock.acquire();
440 }
441
442 private void delay(long duration) {
443 if (duration > 0) {
Vairavan Srinivasane4c56d92011-03-31 13:32:54 -0700444 long bedtime = duration + SystemClock.uptimeMillis();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 do {
446 try {
447 this.wait(duration);
448 }
449 catch (InterruptedException e) {
450 }
451 if (mDone) {
452 break;
453 }
Vairavan Srinivasane4c56d92011-03-31 13:32:54 -0700454 duration = bedtime - SystemClock.uptimeMillis();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 } while (duration > 0);
456 }
457 }
458
459 public void run() {
460 Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
461 synchronized (this) {
462 int index = 0;
Patrick Scott18dd5f02009-07-02 11:31:12 -0400463 long[] pattern = mVibration.mPattern;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 int len = pattern.length;
Patrick Scott18dd5f02009-07-02 11:31:12 -0400465 int repeat = mVibration.mRepeat;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 long duration = 0;
467
468 while (!mDone) {
Eric Olsenf42f15c2009-10-29 16:42:03 -0700469 // add off-time duration to any accumulated on-time duration
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 if (index < len) {
471 duration += pattern[index++];
472 }
473
474 // sleep until it is time to start the vibrator
475 delay(duration);
476 if (mDone) {
477 break;
478 }
479
480 if (index < len) {
481 // read on-time duration and start the vibrator
482 // duration is saved for delay() at top of loop
483 duration = pattern[index++];
484 if (duration > 0) {
Jeff Brown7f6c2312012-04-13 20:38:38 -0700485 VibratorService.this.doVibratorOn(duration);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 }
487 } else {
Patrick Scott18dd5f02009-07-02 11:31:12 -0400488 if (repeat < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800489 break;
490 } else {
Patrick Scott18dd5f02009-07-02 11:31:12 -0400491 index = repeat;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 duration = 0;
493 }
494 }
495 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 mWakeLock.release();
497 }
Patrick Scott18dd5f02009-07-02 11:31:12 -0400498 synchronized (mVibrations) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499 if (mThread == this) {
500 mThread = null;
501 }
Patrick Scott18dd5f02009-07-02 11:31:12 -0400502 if (!mDone) {
503 // If this vibration finished naturally, start the next
504 // vibration.
505 mVibrations.remove(mVibration);
Mathias Jeppssonb23949b2010-09-28 14:45:23 +0200506 unlinkVibration(mVibration);
Patrick Scott18dd5f02009-07-02 11:31:12 -0400507 startNextVibrationLocked();
508 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800509 }
510 }
511 };
512
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
514 public void onReceive(Context context, Intent intent) {
515 if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Patrick Scott18dd5f02009-07-02 11:31:12 -0400516 synchronized (mVibrations) {
517 doCancelVibrateLocked();
Vairavan Srinivasan8a61f492011-05-13 10:47:20 -0700518
519 int size = mVibrations.size();
520 for(int i = 0; i < size; i++) {
521 unlinkVibration(mVibrations.get(i));
522 }
523
Patrick Scott18dd5f02009-07-02 11:31:12 -0400524 mVibrations.clear();
525 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 }
527 }
528 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529}