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