Home | History | Annotate | Download | only in server
      1 /*
      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 
     17 package com.android.server;
     18 
     19 import android.app.AppOpsManager;
     20 import android.content.BroadcastReceiver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.content.pm.PackageManager;
     25 import android.database.ContentObserver;
     26 import android.hardware.input.InputManager;
     27 import android.os.BatteryStats;
     28 import android.os.Handler;
     29 import android.os.IVibratorService;
     30 import android.os.PowerManager;
     31 import android.os.PowerManagerInternal;
     32 import android.os.Process;
     33 import android.os.RemoteException;
     34 import android.os.IBinder;
     35 import android.os.Binder;
     36 import android.os.ServiceManager;
     37 import android.os.SystemClock;
     38 import android.os.UserHandle;
     39 import android.os.Vibrator;
     40 import android.os.WorkSource;
     41 import android.provider.Settings;
     42 import android.provider.Settings.SettingNotFoundException;
     43 import android.util.Slog;
     44 import android.view.InputDevice;
     45 import android.media.AudioAttributes;
     46 
     47 import com.android.internal.app.IAppOpsService;
     48 import com.android.internal.app.IBatteryStats;
     49 
     50 import java.util.ArrayList;
     51 import java.util.Iterator;
     52 import java.util.LinkedList;
     53 import java.util.ListIterator;
     54 
     55 public class VibratorService extends IVibratorService.Stub
     56         implements InputManager.InputDeviceListener {
     57     private static final String TAG = "VibratorService";
     58     private static final boolean DEBUG = false;
     59 
     60     private final LinkedList<Vibration> mVibrations;
     61     private Vibration mCurrentVibration;
     62     private final WorkSource mTmpWorkSource = new WorkSource();
     63     private final Handler mH = new Handler();
     64 
     65     private final Context mContext;
     66     private final PowerManager.WakeLock mWakeLock;
     67     private final IAppOpsService mAppOpsService;
     68     private final IBatteryStats mBatteryStatsService;
     69     private PowerManagerInternal mPowerManagerInternal;
     70     private InputManager mIm;
     71 
     72     volatile VibrateThread mThread;
     73 
     74     // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
     75     // to be acquired
     76     private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
     77     private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
     78     private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
     79 
     80     private int mCurVibUid = -1;
     81     private boolean mLowPowerMode;
     82     private SettingsObserver mSettingObserver;
     83 
     84     native static boolean vibratorExists();
     85     native static void vibratorOn(long milliseconds);
     86     native static void vibratorOff();
     87 
     88     private class Vibration implements IBinder.DeathRecipient {
     89         private final IBinder mToken;
     90         private final long    mTimeout;
     91         private final long    mStartTime;
     92         private final long[]  mPattern;
     93         private final int     mRepeat;
     94         private final int     mUsageHint;
     95         private final int     mUid;
     96         private final String  mOpPkg;
     97 
     98         Vibration(IBinder token, long millis, int usageHint, int uid, String opPkg) {
     99             this(token, millis, null, 0, usageHint, uid, opPkg);
    100         }
    101 
    102         Vibration(IBinder token, long[] pattern, int repeat, int usageHint, int uid,
    103                 String opPkg) {
    104             this(token, 0, pattern, repeat, usageHint, uid, opPkg);
    105         }
    106 
    107         private Vibration(IBinder token, long millis, long[] pattern,
    108                 int repeat, int usageHint, int uid, String opPkg) {
    109             mToken = token;
    110             mTimeout = millis;
    111             mStartTime = SystemClock.uptimeMillis();
    112             mPattern = pattern;
    113             mRepeat = repeat;
    114             mUsageHint = usageHint;
    115             mUid = uid;
    116             mOpPkg = opPkg;
    117         }
    118 
    119         public void binderDied() {
    120             synchronized (mVibrations) {
    121                 mVibrations.remove(this);
    122                 if (this == mCurrentVibration) {
    123                     doCancelVibrateLocked();
    124                     startNextVibrationLocked();
    125                 }
    126             }
    127         }
    128 
    129         public boolean hasLongerTimeout(long millis) {
    130             if (mTimeout == 0) {
    131                 // This is a pattern, return false to play the simple
    132                 // vibration.
    133                 return false;
    134             }
    135             if ((mStartTime + mTimeout)
    136                     < (SystemClock.uptimeMillis() + millis)) {
    137                 // If this vibration will end before the time passed in, let
    138                 // the new vibration play.
    139                 return false;
    140             }
    141             return true;
    142         }
    143 
    144         public boolean isSystemHapticFeedback() {
    145             return (mUid == Process.SYSTEM_UID || mUid == 0) && mRepeat < 0;
    146         }
    147     }
    148 
    149     VibratorService(Context context) {
    150         // Reset the hardware to a default state, in case this is a runtime
    151         // restart instead of a fresh boot.
    152         vibratorOff();
    153 
    154         mContext = context;
    155         PowerManager pm = (PowerManager)context.getSystemService(
    156                 Context.POWER_SERVICE);
    157         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
    158         mWakeLock.setReferenceCounted(true);
    159 
    160         mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
    161         mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
    162                 BatteryStats.SERVICE_NAME));
    163 
    164         mVibrations = new LinkedList<Vibration>();
    165 
    166         IntentFilter filter = new IntentFilter();
    167         filter.addAction(Intent.ACTION_SCREEN_OFF);
    168         context.registerReceiver(mIntentReceiver, filter);
    169     }
    170 
    171     public void systemReady() {
    172         mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);
    173         mSettingObserver = new SettingsObserver(mH);
    174 
    175         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
    176         mPowerManagerInternal.registerLowPowerModeObserver(
    177                 new PowerManagerInternal.LowPowerModeListener() {
    178             @Override
    179             public void onLowPowerModeChanged(boolean enabled) {
    180                 updateInputDeviceVibrators();
    181             }
    182         });
    183 
    184         mContext.getContentResolver().registerContentObserver(
    185                 Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES),
    186                 true, mSettingObserver, UserHandle.USER_ALL);
    187 
    188         mContext.registerReceiver(new BroadcastReceiver() {
    189             @Override
    190             public void onReceive(Context context, Intent intent) {
    191                 updateInputDeviceVibrators();
    192             }
    193         }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
    194 
    195         updateInputDeviceVibrators();
    196     }
    197 
    198     private final class SettingsObserver extends ContentObserver {
    199         public SettingsObserver(Handler handler) {
    200             super(handler);
    201         }
    202 
    203         @Override
    204         public void onChange(boolean SelfChange) {
    205             updateInputDeviceVibrators();
    206         }
    207     }
    208 
    209     @Override // Binder call
    210     public boolean hasVibrator() {
    211         return doVibratorExists();
    212     }
    213 
    214     private void verifyIncomingUid(int uid) {
    215         if (uid == Binder.getCallingUid()) {
    216             return;
    217         }
    218         if (Binder.getCallingPid() == Process.myPid()) {
    219             return;
    220         }
    221         mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
    222                 Binder.getCallingPid(), Binder.getCallingUid(), null);
    223     }
    224 
    225     @Override // Binder call
    226     public void vibrate(int uid, String opPkg, long milliseconds, int usageHint,
    227             IBinder token) {
    228         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
    229                 != PackageManager.PERMISSION_GRANTED) {
    230             throw new SecurityException("Requires VIBRATE permission");
    231         }
    232         verifyIncomingUid(uid);
    233         // We're running in the system server so we cannot crash. Check for a
    234         // timeout of 0 or negative. This will ensure that a vibration has
    235         // either a timeout of > 0 or a non-null pattern.
    236         if (milliseconds <= 0 || (mCurrentVibration != null
    237                 && mCurrentVibration.hasLongerTimeout(milliseconds))) {
    238             // Ignore this vibration since the current vibration will play for
    239             // longer than milliseconds.
    240             return;
    241         }
    242 
    243         if (DEBUG) {
    244             Slog.d(TAG, "Vibrating for " + milliseconds + " ms.");
    245         }
    246 
    247         Vibration vib = new Vibration(token, milliseconds, usageHint, uid, opPkg);
    248 
    249         final long ident = Binder.clearCallingIdentity();
    250         try {
    251             synchronized (mVibrations) {
    252                 removeVibrationLocked(token);
    253                 doCancelVibrateLocked();
    254                 mCurrentVibration = vib;
    255                 startVibrationLocked(vib);
    256             }
    257         } finally {
    258             Binder.restoreCallingIdentity(ident);
    259         }
    260     }
    261 
    262     private boolean isAll0(long[] pattern) {
    263         int N = pattern.length;
    264         for (int i = 0; i < N; i++) {
    265             if (pattern[i] != 0) {
    266                 return false;
    267             }
    268         }
    269         return true;
    270     }
    271 
    272     @Override // Binder call
    273     public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
    274             int usageHint, IBinder token) {
    275         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
    276                 != PackageManager.PERMISSION_GRANTED) {
    277             throw new SecurityException("Requires VIBRATE permission");
    278         }
    279         verifyIncomingUid(uid);
    280         // so wakelock calls will succeed
    281         long identity = Binder.clearCallingIdentity();
    282         try {
    283             if (DEBUG) {
    284                 String s = "";
    285                 int N = pattern.length;
    286                 for (int i=0; i<N; i++) {
    287                     s += " " + pattern[i];
    288                 }
    289                 Slog.d(TAG, "Vibrating with pattern:" + s);
    290             }
    291 
    292             // we're running in the server so we can't fail
    293             if (pattern == null || pattern.length == 0
    294                     || isAll0(pattern)
    295                     || repeat >= pattern.length || token == null) {
    296                 return;
    297             }
    298 
    299             Vibration vib = new Vibration(token, pattern, repeat, usageHint, uid, packageName);
    300             try {
    301                 token.linkToDeath(vib, 0);
    302             } catch (RemoteException e) {
    303                 return;
    304             }
    305 
    306             synchronized (mVibrations) {
    307                 removeVibrationLocked(token);
    308                 doCancelVibrateLocked();
    309                 if (repeat >= 0) {
    310                     mVibrations.addFirst(vib);
    311                     startNextVibrationLocked();
    312                 } else {
    313                     // A negative repeat means that this pattern is not meant
    314                     // to repeat. Treat it like a simple vibration.
    315                     mCurrentVibration = vib;
    316                     startVibrationLocked(vib);
    317                 }
    318             }
    319         }
    320         finally {
    321             Binder.restoreCallingIdentity(identity);
    322         }
    323     }
    324 
    325     @Override // Binder call
    326     public void cancelVibrate(IBinder token) {
    327         mContext.enforceCallingOrSelfPermission(
    328                 android.Manifest.permission.VIBRATE,
    329                 "cancelVibrate");
    330 
    331         // so wakelock calls will succeed
    332         long identity = Binder.clearCallingIdentity();
    333         try {
    334             synchronized (mVibrations) {
    335                 final Vibration vib = removeVibrationLocked(token);
    336                 if (vib == mCurrentVibration) {
    337                     if (DEBUG) {
    338                         Slog.d(TAG, "Canceling vibration.");
    339                     }
    340                     doCancelVibrateLocked();
    341                     startNextVibrationLocked();
    342                 }
    343             }
    344         }
    345         finally {
    346             Binder.restoreCallingIdentity(identity);
    347         }
    348     }
    349 
    350     private final Runnable mVibrationRunnable = new Runnable() {
    351         @Override
    352         public void run() {
    353             synchronized (mVibrations) {
    354                 doCancelVibrateLocked();
    355                 startNextVibrationLocked();
    356             }
    357         }
    358     };
    359 
    360     // Lock held on mVibrations
    361     private void doCancelVibrateLocked() {
    362         if (mThread != null) {
    363             synchronized (mThread) {
    364                 mThread.mDone = true;
    365                 mThread.notify();
    366             }
    367             mThread = null;
    368         }
    369         doVibratorOff();
    370         mH.removeCallbacks(mVibrationRunnable);
    371         reportFinishVibrationLocked();
    372     }
    373 
    374     // Lock held on mVibrations
    375     private void startNextVibrationLocked() {
    376         if (mVibrations.size() <= 0) {
    377             reportFinishVibrationLocked();
    378             mCurrentVibration = null;
    379             return;
    380         }
    381         mCurrentVibration = mVibrations.getFirst();
    382         startVibrationLocked(mCurrentVibration);
    383     }
    384 
    385     // Lock held on mVibrations
    386     private void startVibrationLocked(final Vibration vib) {
    387         try {
    388             if (mLowPowerMode
    389                     && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
    390                 return;
    391             }
    392 
    393             int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
    394                     vib.mUsageHint, vib.mUid, vib.mOpPkg);
    395             if (mode == AppOpsManager.MODE_ALLOWED) {
    396                 mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
    397                     AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
    398             }
    399             if (mode != AppOpsManager.MODE_ALLOWED) {
    400                 if (mode == AppOpsManager.MODE_ERRORED) {
    401                     Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
    402                 }
    403                 mH.post(mVibrationRunnable);
    404                 return;
    405             }
    406         } catch (RemoteException e) {
    407         }
    408         if (vib.mTimeout != 0) {
    409             doVibratorOn(vib.mTimeout, vib.mUid, vib.mUsageHint);
    410             mH.postDelayed(mVibrationRunnable, vib.mTimeout);
    411         } else {
    412             // mThread better be null here. doCancelVibrate should always be
    413             // called before startNextVibrationLocked or startVibrationLocked.
    414             mThread = new VibrateThread(vib);
    415             mThread.start();
    416         }
    417     }
    418 
    419     private void reportFinishVibrationLocked() {
    420         if (mCurrentVibration != null) {
    421             try {
    422                 mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
    423                         AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
    424                         mCurrentVibration.mOpPkg);
    425             } catch (RemoteException e) {
    426             }
    427             mCurrentVibration = null;
    428         }
    429     }
    430 
    431     // Lock held on mVibrations
    432     private Vibration removeVibrationLocked(IBinder token) {
    433         ListIterator<Vibration> iter = mVibrations.listIterator(0);
    434         while (iter.hasNext()) {
    435             Vibration vib = iter.next();
    436             if (vib.mToken == token) {
    437                 iter.remove();
    438                 unlinkVibration(vib);
    439                 return vib;
    440             }
    441         }
    442         // We might be looking for a simple vibration which is only stored in
    443         // mCurrentVibration.
    444         if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
    445             unlinkVibration(mCurrentVibration);
    446             return mCurrentVibration;
    447         }
    448         return null;
    449     }
    450 
    451     private void unlinkVibration(Vibration vib) {
    452         if (vib.mPattern != null) {
    453             // If Vibration object has a pattern,
    454             // the Vibration object has also been linkedToDeath.
    455             vib.mToken.unlinkToDeath(vib, 0);
    456         }
    457     }
    458 
    459     private void updateInputDeviceVibrators() {
    460         synchronized (mVibrations) {
    461             doCancelVibrateLocked();
    462 
    463             synchronized (mInputDeviceVibrators) {
    464                 mVibrateInputDevicesSetting = false;
    465                 try {
    466                     mVibrateInputDevicesSetting = Settings.System.getIntForUser(
    467                             mContext.getContentResolver(),
    468                             Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
    469                 } catch (SettingNotFoundException snfe) {
    470                 }
    471 
    472                 mLowPowerMode = mPowerManagerInternal.getLowPowerModeEnabled();
    473 
    474                 if (mVibrateInputDevicesSetting) {
    475                     if (!mInputDeviceListenerRegistered) {
    476                         mInputDeviceListenerRegistered = true;
    477                         mIm.registerInputDeviceListener(this, mH);
    478                     }
    479                 } else {
    480                     if (mInputDeviceListenerRegistered) {
    481                         mInputDeviceListenerRegistered = false;
    482                         mIm.unregisterInputDeviceListener(this);
    483                     }
    484                 }
    485 
    486                 mInputDeviceVibrators.clear();
    487                 if (mVibrateInputDevicesSetting) {
    488                     int[] ids = mIm.getInputDeviceIds();
    489                     for (int i = 0; i < ids.length; i++) {
    490                         InputDevice device = mIm.getInputDevice(ids[i]);
    491                         Vibrator vibrator = device.getVibrator();
    492                         if (vibrator.hasVibrator()) {
    493                             mInputDeviceVibrators.add(vibrator);
    494                         }
    495                     }
    496                 }
    497             }
    498 
    499             startNextVibrationLocked();
    500         }
    501     }
    502 
    503     @Override
    504     public void onInputDeviceAdded(int deviceId) {
    505         updateInputDeviceVibrators();
    506     }
    507 
    508     @Override
    509     public void onInputDeviceChanged(int deviceId) {
    510         updateInputDeviceVibrators();
    511     }
    512 
    513     @Override
    514     public void onInputDeviceRemoved(int deviceId) {
    515         updateInputDeviceVibrators();
    516     }
    517 
    518     private boolean doVibratorExists() {
    519         // For now, we choose to ignore the presence of input devices that have vibrators
    520         // when reporting whether the device has a vibrator.  Applications often use this
    521         // information to decide whether to enable certain features so they expect the
    522         // result of hasVibrator() to be constant.  For now, just report whether
    523         // the device has a built-in vibrator.
    524         //synchronized (mInputDeviceVibrators) {
    525         //    return !mInputDeviceVibrators.isEmpty() || vibratorExists();
    526         //}
    527         return vibratorExists();
    528     }
    529 
    530     private void doVibratorOn(long millis, int uid, int usageHint) {
    531         synchronized (mInputDeviceVibrators) {
    532             if (DEBUG) {
    533                 Slog.d(TAG, "Turning vibrator on for " + millis + " ms.");
    534             }
    535             try {
    536                 mBatteryStatsService.noteVibratorOn(uid, millis);
    537                 mCurVibUid = uid;
    538             } catch (RemoteException e) {
    539             }
    540             final int vibratorCount = mInputDeviceVibrators.size();
    541             if (vibratorCount != 0) {
    542                 final AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usageHint)
    543                         .build();
    544                 for (int i = 0; i < vibratorCount; i++) {
    545                     mInputDeviceVibrators.get(i).vibrate(millis, attributes);
    546                 }
    547             } else {
    548                 vibratorOn(millis);
    549             }
    550         }
    551     }
    552 
    553     private void doVibratorOff() {
    554         synchronized (mInputDeviceVibrators) {
    555             if (DEBUG) {
    556                 Slog.d(TAG, "Turning vibrator off.");
    557             }
    558             if (mCurVibUid >= 0) {
    559                 try {
    560                     mBatteryStatsService.noteVibratorOff(mCurVibUid);
    561                 } catch (RemoteException e) {
    562                 }
    563                 mCurVibUid = -1;
    564             }
    565             final int vibratorCount = mInputDeviceVibrators.size();
    566             if (vibratorCount != 0) {
    567                 for (int i = 0; i < vibratorCount; i++) {
    568                     mInputDeviceVibrators.get(i).cancel();
    569                 }
    570             } else {
    571                 vibratorOff();
    572             }
    573         }
    574     }
    575 
    576     private class VibrateThread extends Thread {
    577         final Vibration mVibration;
    578         boolean mDone;
    579 
    580         VibrateThread(Vibration vib) {
    581             mVibration = vib;
    582             mTmpWorkSource.set(vib.mUid);
    583             mWakeLock.setWorkSource(mTmpWorkSource);
    584             mWakeLock.acquire();
    585         }
    586 
    587         private void delay(long duration) {
    588             if (duration > 0) {
    589                 long bedtime = duration + SystemClock.uptimeMillis();
    590                 do {
    591                     try {
    592                         this.wait(duration);
    593                     }
    594                     catch (InterruptedException e) {
    595                     }
    596                     if (mDone) {
    597                         break;
    598                     }
    599                     duration = bedtime - SystemClock.uptimeMillis();
    600                 } while (duration > 0);
    601             }
    602         }
    603 
    604         public void run() {
    605             Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
    606             synchronized (this) {
    607                 final long[] pattern = mVibration.mPattern;
    608                 final int len = pattern.length;
    609                 final int repeat = mVibration.mRepeat;
    610                 final int uid = mVibration.mUid;
    611                 final int usageHint = mVibration.mUsageHint;
    612                 int index = 0;
    613                 long duration = 0;
    614 
    615                 while (!mDone) {
    616                     // add off-time duration to any accumulated on-time duration
    617                     if (index < len) {
    618                         duration += pattern[index++];
    619                     }
    620 
    621                     // sleep until it is time to start the vibrator
    622                     delay(duration);
    623                     if (mDone) {
    624                         break;
    625                     }
    626 
    627                     if (index < len) {
    628                         // read on-time duration and start the vibrator
    629                         // duration is saved for delay() at top of loop
    630                         duration = pattern[index++];
    631                         if (duration > 0) {
    632                             VibratorService.this.doVibratorOn(duration, uid, usageHint);
    633                         }
    634                     } else {
    635                         if (repeat < 0) {
    636                             break;
    637                         } else {
    638                             index = repeat;
    639                             duration = 0;
    640                         }
    641                     }
    642                 }
    643                 mWakeLock.release();
    644             }
    645             synchronized (mVibrations) {
    646                 if (mThread == this) {
    647                     mThread = null;
    648                 }
    649                 if (!mDone) {
    650                     // If this vibration finished naturally, start the next
    651                     // vibration.
    652                     mVibrations.remove(mVibration);
    653                     unlinkVibration(mVibration);
    654                     startNextVibrationLocked();
    655                 }
    656             }
    657         }
    658     }
    659 
    660     BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
    661         @Override
    662         public void onReceive(Context context, Intent intent) {
    663             if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
    664                 synchronized (mVibrations) {
    665                     // When the system is entering a non-interactive state, we want
    666                     // to cancel vibrations in case a misbehaving app has abandoned
    667                     // them.  However it may happen that the system is currently playing
    668                     // haptic feedback as part of the transition.  So we don't cancel
    669                     // system vibrations.
    670                     if (mCurrentVibration != null
    671                             && !mCurrentVibration.isSystemHapticFeedback()) {
    672                         doCancelVibrateLocked();
    673                     }
    674 
    675                     // Clear all remaining vibrations.
    676                     Iterator<Vibration> it = mVibrations.iterator();
    677                     while (it.hasNext()) {
    678                         Vibration vibration = it.next();
    679                         if (vibration != mCurrentVibration) {
    680                             unlinkVibration(vibration);
    681                             it.remove();
    682                         }
    683                     }
    684                 }
    685             }
    686         }
    687     };
    688 }
    689