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