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