content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server;
  
  import android.annotation.Nullable;
  import android.app.ActivityManager;
  import android.app.AppOpsManager;
  import android.app.IUidObserver;
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.PackageManager;
  import android.content.res.Resources;
  import android.database.ContentObserver;
  import android.hardware.input.InputManager;
  import android.hardware.vibrator.V1_0.EffectStrength;
  import android.icu.text.DateFormat;
  import android.media.AudioAttributes;
  import android.media.AudioManager;
  import android.os.BatteryStats;
  import android.os.Binder;
  import android.os.ExternalVibration;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.IExternalVibratorService;
  import android.os.IVibratorService;
  import android.os.PowerManager;
  import android.os.PowerManager.ServiceType;
  import android.os.PowerManagerInternal;
  import android.os.PowerSaveState;
  import android.os.Process;
  import android.os.RemoteException;
  import android.os.ResultReceiver;
  import android.os.ServiceManager;
  import android.os.ShellCallback;
  import android.os.ShellCommand;
  import android.os.SystemClock;
  import android.os.Trace;
  import android.os.UserHandle;
  import android.os.VibrationEffect;
  import android.os.Vibrator;
  import android.os.WorkSource;
  import android.provider.DeviceConfig;
  import android.provider.Settings;
  import android.provider.Settings.SettingNotFoundException;
  import android.util.DebugUtils;
  import android.util.Slog;
  import android.util.SparseArray;
  import android.util.StatsLog;
  import android.view.InputDevice;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.app.IBatteryStats;
  import com.android.internal.util.DumpUtils;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.LinkedList;
  
  public class VibratorService extends IVibratorService.Stub
          implements InputManager.InputDeviceListener {
      private static final String TAG = "VibratorService";
      private static final boolean DEBUG = false;
      private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
      private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
      private static final String RAMPING_RINGER_ENABLED = "ramping_ringer_enabled";
  
      private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
  
      // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
      // and the default intensity for that type of vibration (i.e. current - default).
      private static final int SCALE_MUTE = IExternalVibratorService.SCALE_MUTE; // -100
      private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2
      private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1
      private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0
      private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1
      private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2
  
      // Gamma adjustments for scale levels.
      private static final float SCALE_VERY_LOW_GAMMA = 2.0f;
      private static final float SCALE_LOW_GAMMA = 1.5f;
      private static final float SCALE_NONE_GAMMA = 1.0f;
      private static final float SCALE_HIGH_GAMMA = 0.5f;
      private static final float SCALE_VERY_HIGH_GAMMA = 0.25f;
  
      // Max amplitudes for scale levels. If one is not listed, then the max amplitude is the default
      // max amplitude.
      private static final int SCALE_VERY_LOW_MAX_AMPLITUDE = 168; // 2/3 * 255
      private static final int SCALE_LOW_MAX_AMPLITUDE = 192; // 3/4 * 255
  
      // If a vibration is playing for longer than 5s, it's probably not haptic feedback.
      private static final long MAX_HAPTIC_FEEDBACK_DURATION = 5000;
  
  
      // A mapping from the intensity adjustment to the scaling to apply, where the intensity
      // adjustment is defined as the delta between the default intensity level and the user selected
      // intensity level. It's important that we apply the scaling on the delta between the two so
      // that the default intensity level applies no scaling to application provided effects.
      private final SparseArray<ScaleLevel> mScaleLevels;
      private final LinkedList<VibrationInfo> mPreviousRingVibrations;
      private final LinkedList<VibrationInfo> mPreviousNotificationVibrations;
      private final LinkedList<VibrationInfo> mPreviousAlarmVibrations;
      private final LinkedList<ExternalVibration> mPreviousExternalVibrations;
      private final LinkedList<VibrationInfo> mPreviousVibrations;
      private final int mPreviousVibrationsLimit;
      private final boolean mAllowPriorityVibrationsInLowPowerMode;
      private final boolean mSupportsAmplitudeControl;
      private final boolean mSupportsExternalControl;
      private final int mDefaultVibrationAmplitude;
      private final SparseArray<VibrationEffect> mFallbackEffects;
      private final SparseArray<Integer> mProcStatesCache = new SparseArray();
      private final WorkSource mTmpWorkSource = new WorkSource();
      private final Handler mH = new Handler();
      private final Object mLock = new Object();
  
      private final Context mContext;
      private final PowerManager.WakeLock mWakeLock;
      private final AppOpsManager mAppOps;
      private final IBatteryStats mBatteryStatsService;
      private PowerManagerInternal mPowerManagerInternal;
      private InputManager mIm;
      private Vibrator mVibrator;
      private SettingsObserver mSettingObserver;
  
      private volatile VibrateThread mThread;
  
      // mInputDeviceVibrators lock should be acquired after mLock, if both are
      // to be acquired
      private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
      private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
      private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
  
      @GuardedBy("mLock")
      private Vibration mCurrentVibration;
      private int mCurVibUid = -1;
      private ExternalVibration mCurrentExternalVibration;
      private boolean mVibratorUnderExternalControl;
      private boolean mLowPowerMode;
      private int mHapticFeedbackIntensity;
      private int mNotificationIntensity;
      private int mRingIntensity;
  
      static native boolean vibratorExists();
      static native void vibratorInit();
      static native void vibratorOn(long milliseconds);
      static native void vibratorOff();
      static native boolean vibratorSupportsAmplitudeControl();
      static native void vibratorSetAmplitude(int amplitude);
      static native long vibratorPerformEffect(long effect, long strength);
      static native boolean vibratorSupportsExternalControl();
      static native void vibratorSetExternalControl(boolean enabled);
  
      private final IUidObserver mUidObserver = new IUidObserver.Stub() {
          @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
              mProcStatesCache.put(uid, procState);
          }
  
          @Override public void onUidGone(int uid, boolean disabled) {
              mProcStatesCache.delete(uid);
          }
  
          @Override public void onUidActive(int uid) {
          }
  
          @Override public void onUidIdle(int uid, boolean disabled) {
          }
  
          @Override public void onUidCachedChanged(int uid, boolean cached) {
          }
      };
  
      private class Vibration implements IBinder.DeathRecipient {
          public final IBinder token;
          // Start time in CLOCK_BOOTTIME base.
          public final long startTime;
          // Start time in unix epoch time. Only to be used for debugging purposes and to correlate
          // with other system events, any duration calculations should be done use startTime so as
          // not to be affected by discontinuities created by RTC adjustments.
          public final long startTimeDebug;
          public final AudioAttributes attrs;
          public final int uid;
          public final String opPkg;
          public final String reason;
  
          // The actual effect to be played.
          public VibrationEffect effect;
          // The original effect that was requested. This is non-null only when the original effect
          // differs from the effect that's being played. Typically these two things differ because
          // the effect was scaled based on the users vibration intensity settings.
          public VibrationEffect originalEffect;
  
          private Vibration(IBinder token, VibrationEffect effect,
                  AudioAttributes attrs, int uid, String opPkg, String reason) {
              this.token = token;
              this.effect = effect;
              this.startTime = SystemClock.elapsedRealtime();
              this.startTimeDebug = System.currentTimeMillis();
              this.attrs = attrs;
              this.uid = uid;
              this.opPkg = opPkg;
              this.reason = reason;
          }
  
          public void binderDied() {
              synchronized (mLock) {
                  if (this == mCurrentVibration) {
                      doCancelVibrateLocked();
                  }
              }
          }
  
          public boolean hasTimeoutLongerThan(long millis) {
              final long duration = effect.getDuration();
              return duration >= 0 && duration > millis;
          }
  
          public boolean isHapticFeedback() {
              if (VibratorService.this.isHapticFeedback(attrs.getUsage())) {
                  return true;
              }
              if (effect instanceof VibrationEffect.Prebaked) {
                  VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
                  switch (prebaked.getId()) {
                      case VibrationEffect.EFFECT_CLICK:
                      case VibrationEffect.EFFECT_DOUBLE_CLICK:
                      case VibrationEffect.EFFECT_HEAVY_CLICK:
                      case VibrationEffect.EFFECT_TEXTURE_TICK:
                      case VibrationEffect.EFFECT_TICK:
                      case VibrationEffect.EFFECT_POP:
                      case VibrationEffect.EFFECT_THUD:
                          return true;
                      default:
                          Slog.w(TAG, "Unknown prebaked vibration effect, "
                                  + "assuming it isn't haptic feedback.");
                          return false;
                  }
              }
              final long duration = effect.getDuration();
              return duration >= 0 && duration < MAX_HAPTIC_FEEDBACK_DURATION;
          }
  
          public boolean isNotification() {
              return VibratorService.this.isNotification(attrs.getUsage());
          }
  
          public boolean isRingtone() {
              return VibratorService.this.isRingtone(attrs.getUsage());
          }
  
          public boolean isAlarm() {
              return VibratorService.this.isAlarm(attrs.getUsage());
          }
  
          public boolean isFromSystem() {
              return uid == Process.SYSTEM_UID || uid == 0 || SYSTEM_UI_PACKAGE.equals(opPkg);
          }
  
          public VibrationInfo toInfo() {
              return new VibrationInfo(
                      startTimeDebug, effect, originalEffect, attrs, uid, opPkg, reason);
          }
      }
  
      private static class VibrationInfo {
          private final long mStartTimeDebug;
          private final VibrationEffect mEffect;
          private final VibrationEffect mOriginalEffect;
          private final AudioAttributes mAttrs;
          private final int mUid;
          private final String mOpPkg;
          private final String mReason;
  
          public VibrationInfo(long startTimeDebug, VibrationEffect effect,
                  VibrationEffect originalEffect, AudioAttributes attrs, int uid,
                  String opPkg, String reason) {
              mStartTimeDebug = startTimeDebug;
              mEffect = effect;
              mOriginalEffect = originalEffect;
              mAttrs = attrs;
              mUid = uid;
              mOpPkg = opPkg;
              mReason = reason;
          }
  
          @Override
          public String toString() {
              return new StringBuilder()
                      .append("startTime: ")
                      .append(DateFormat.getDateTimeInstance().format(new Date(mStartTimeDebug)))
                      .append(", effect: ")
                      .append(mEffect)
                      .append(", originalEffect: ")
                      .append(mOriginalEffect)
                      .append(", attrs: ")
                      .append(mAttrs)
                      .append(", uid: ")
                      .append(mUid)
                      .append(", opPkg: ")
                      .append(mOpPkg)
                      .append(", reason: ")
                      .append(mReason)
                      .toString();
          }
      }
  
      private static final class ScaleLevel {
          public final float gamma;
          public final int maxAmplitude;
  
          public ScaleLevel(float gamma) {
              this(gamma, VibrationEffect.MAX_AMPLITUDE);
          }
  
          public ScaleLevel(float gamma, int maxAmplitude) {
              this.gamma = gamma;
              this.maxAmplitude = maxAmplitude;
          }
  
          @Override
          public String toString() {
              return "ScaleLevel{gamma=" + gamma + ", maxAmplitude=" + maxAmplitude + "}";
          }
      }
  
      VibratorService(Context context) {
          vibratorInit();
          // Reset the hardware to a default state, in case this is a runtime
          // restart instead of a fresh boot.
          vibratorOff();
  
          mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
          mSupportsExternalControl = vibratorSupportsExternalControl();
  
          mContext = context;
          PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
          mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
          mWakeLock.setReferenceCounted(true);
  
          mAppOps = mContext.getSystemService(AppOpsManager.class);
          mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                  BatteryStats.SERVICE_NAME));
  
          mPreviousVibrationsLimit = mContext.getResources().getInteger(
                  com.android.internal.R.integer.config_previousVibrationsDumpLimit);
  
          mDefaultVibrationAmplitude = mContext.getResources().getInteger(
                  com.android.internal.R.integer.config_defaultVibrationAmplitude);
  
          mAllowPriorityVibrationsInLowPowerMode = mContext.getResources().getBoolean(
                  com.android.internal.R.bool.config_allowPriorityVibrationsInLowPowerMode);
  
          mPreviousRingVibrations = new LinkedList<>();
          mPreviousNotificationVibrations = new LinkedList<>();
          mPreviousAlarmVibrations = new LinkedList<>();
          mPreviousVibrations = new LinkedList<>();
          mPreviousExternalVibrations = new LinkedList<>();
  
          IntentFilter filter = new IntentFilter();
          filter.addAction(Intent.ACTION_SCREEN_OFF);
          context.registerReceiver(mIntentReceiver, filter);
  
          VibrationEffect clickEffect = createEffectFromResource(
                  com.android.internal.R.array.config_virtualKeyVibePattern);
          VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
                  DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS, -1 /*repeatIndex*/);
          VibrationEffect heavyClickEffect = createEffectFromResource(
                  com.android.internal.R.array.config_longPressVibePattern);
          VibrationEffect tickEffect = createEffectFromResource(
                  com.android.internal.R.array.config_clockTickVibePattern);
  
          mFallbackEffects = new SparseArray<>();
          mFallbackEffects.put(VibrationEffect.EFFECT_CLICK, clickEffect);
          mFallbackEffects.put(VibrationEffect.EFFECT_DOUBLE_CLICK, doubleClickEffect);
          mFallbackEffects.put(VibrationEffect.EFFECT_TICK, tickEffect);
          mFallbackEffects.put(VibrationEffect.EFFECT_HEAVY_CLICK, heavyClickEffect);
  
          mFallbackEffects.put(VibrationEffect.EFFECT_TEXTURE_TICK,
                  VibrationEffect.get(VibrationEffect.EFFECT_TICK, false));
  
          mScaleLevels = new SparseArray<>();
          mScaleLevels.put(SCALE_VERY_LOW,
                  new ScaleLevel(SCALE_VERY_LOW_GAMMA, SCALE_VERY_LOW_MAX_AMPLITUDE));
          mScaleLevels.put(SCALE_LOW, new ScaleLevel(SCALE_LOW_GAMMA, SCALE_LOW_MAX_AMPLITUDE));
          mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_NONE_GAMMA));
          mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_HIGH_GAMMA));
          mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_VERY_HIGH_GAMMA));
  
          ServiceManager.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
      }
  
      private VibrationEffect createEffectFromResource(int resId) {
          long[] timings = getLongIntArray(mContext.getResources(), resId);
          return createEffectFromTimings(timings);
      }
  
      private static VibrationEffect createEffectFromTimings(long[] timings) {
          if (timings == null || timings.length == 0) {
              return null;
          } else if (timings.length == 1) {
              return VibrationEffect.createOneShot(timings[0], VibrationEffect.DEFAULT_AMPLITUDE);
          } else {
              return VibrationEffect.createWaveform(timings, -1);
          }
      }
  
      public void systemReady() {
          Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady");
          try {
              mIm = mContext.getSystemService(InputManager.class);
              mVibrator = mContext.getSystemService(Vibrator.class);
              mSettingObserver = new SettingsObserver(mH);
  
              mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
              mPowerManagerInternal.registerLowPowerModeObserver(
                      new PowerManagerInternal.LowPowerModeListener() {
                          @Override
                          public int getServiceType() {
                              return ServiceType.VIBRATION;
                          }
  
                          @Override
                          public void onLowPowerModeChanged(PowerSaveState result) {
                              updateVibrators();
                          }
              });
  
              mContext.getContentResolver().registerContentObserver(
                      Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES),
                      true, mSettingObserver, UserHandle.USER_ALL);
  
              mContext.getContentResolver().registerContentObserver(
                      Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY),
                      true, mSettingObserver, UserHandle.USER_ALL);
  
              mContext.getContentResolver().registerContentObserver(
                      Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY),
                      true, mSettingObserver, UserHandle.USER_ALL);
  
              mContext.getContentResolver().registerContentObserver(
                      Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY),
                      true, mSettingObserver, UserHandle.USER_ALL);
  
              mContext.registerReceiver(new BroadcastReceiver() {
                  @Override
                  public void onReceive(Context context, Intent intent) {
                      updateVibrators();
                  }
              }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
  
              try {
                  ActivityManager.getService().registerUidObserver(mUidObserver,
                          ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
                          ActivityManager.PROCESS_STATE_UNKNOWN, null);
              } catch (RemoteException e) {
                  // ignored; both services live in system_server
              }
  
              updateVibrators();
          } finally {
              Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
          }
      }
  
      private final class SettingsObserver extends ContentObserver {
          public SettingsObserver(Handler handler) {
              super(handler);
          }
  
          @Override
          public void onChange(boolean SelfChange) {
              updateVibrators();
          }
      }
  
      @Override // Binder call
      public boolean hasVibrator() {
          return doVibratorExists();
      }
  
      @Override // Binder call
      public boolean hasAmplitudeControl() {
          synchronized (mInputDeviceVibrators) {
              // Input device vibrators don't support amplitude controls yet, but are still used over
              // the system vibrator when connected.
              return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
          }
      }
  
      private void verifyIncomingUid(int uid) {
          if (uid == Binder.getCallingUid()) {
              return;
          }
          if (Binder.getCallingPid() == Process.myPid()) {
              return;
          }
          mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
                  Binder.getCallingPid(), Binder.getCallingUid(), null);
      }
  
      /**
       * Validate the incoming VibrationEffect.
       *
       * We can't throw exceptions here since we might be called from some system_server component,
       * which would bring the whole system down.
       *
       * @return whether the VibrationEffect is valid
       */
      private static boolean verifyVibrationEffect(VibrationEffect effect) {
          if (effect == null) {
              // Effect must not be null.
              Slog.wtf(TAG, "effect must not be null");
              return false;
          }
          try {
              effect.validate();
          } catch (Exception e) {
              Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
              return false;
          }
          return true;
      }
  
      private static long[] getLongIntArray(Resources r, int resid) {
          int[] ar = r.getIntArray(resid);
          if (ar == null) {
              return null;
          }
          long[] out = new long[ar.length];
          for (int i = 0; i < ar.length; i++) {
              out[i] = ar[i];
          }
          return out;
      }
  
      @Override // Binder call
      public void vibrate(int uid, String opPkg, VibrationEffect effect,
              @Nullable AudioAttributes attrs, String reason, IBinder token) {
          Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
          try {
              if (!hasPermission(android.Manifest.permission.VIBRATE)) {
                  throw new SecurityException("Requires VIBRATE permission");
              }
              if (token == null) {
                  Slog.e(TAG, "token must not be null");
                  return;
              }
              verifyIncomingUid(uid);
              if (!verifyVibrationEffect(effect)) {
                  return;
              }
  
              if (attrs == null) {
                  attrs = new AudioAttributes.Builder()
                          .setUsage(AudioAttributes.USAGE_UNKNOWN)
                          .build();
              }
  
              if (shouldBypassDnd(attrs)) {
                  if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
                          || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
                          || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
                      final int flags = attrs.getAllFlags()
                              & ~AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
                      attrs = new AudioAttributes.Builder(attrs).replaceFlags(flags).build();
                  }
              }
  
              // If our current vibration is longer than the new vibration and is the same amplitude,
              // then just let the current one finish.
              synchronized (mLock) {
                  if (effect instanceof VibrationEffect.OneShot
                          && mCurrentVibration != null
                          && mCurrentVibration.effect instanceof VibrationEffect.OneShot) {
                      VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
                      VibrationEffect.OneShot currentOneShot =
                              (VibrationEffect.OneShot) mCurrentVibration.effect;
                      if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())
                              && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
                          if (DEBUG) {
                              Slog.d(TAG,
                                      "Ignoring incoming vibration in favor of current vibration");
                          }
                          return;
                      }
                  }
  
  
                  // If something has external control of the vibrator, assume that it's more
                  // important for now.
                  if (mCurrentExternalVibration != null) {
                      if (DEBUG) {
                          Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
                      }
                      return;
                  }
  
                  // If the current vibration is repeating and the incoming one is non-repeating,
                  // then ignore the non-repeating vibration. This is so that we don't cancel
                  // vibrations that are meant to grab the attention of the user, like ringtones and
                  // alarms, in favor of one-shot vibrations that are likely quite short.
                  if (!isRepeatingVibration(effect)
                          && mCurrentVibration != null
                          && isRepeatingVibration(mCurrentVibration.effect)) {
                      if (DEBUG) {
                          Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
                      }
                      return;
                  }
  
                  Vibration vib = new Vibration(token, effect, attrs, uid, opPkg, reason);
                  if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
                          > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
                          && !vib.isNotification() && !vib.isRingtone() && !vib.isAlarm()) {
                      Slog.e(TAG, "Ignoring incoming vibration as process with"
                              + " uid= " + uid + " is background,"
                              + " attrs= " + vib.attrs);
                      return;
                  }
                  linkVibration(vib);
                  long ident = Binder.clearCallingIdentity();
                  try {
                      doCancelVibrateLocked();
                      startVibrationLocked(vib);
                      addToPreviousVibrationsLocked(vib);
                  } finally {
                      Binder.restoreCallingIdentity(ident);
                  }
              }
          } finally {
              Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
          }
      }
  
      private boolean hasPermission(String permission) {
          return mContext.checkCallingOrSelfPermission(permission)
                  == PackageManager.PERMISSION_GRANTED;
      }
  
      private static boolean isRepeatingVibration(VibrationEffect effect) {
          return effect.getDuration() == Long.MAX_VALUE;
      }
  
      private void addToPreviousVibrationsLocked(Vibration vib) {
          final LinkedList<VibrationInfo> previousVibrations;
          if (vib.isRingtone()) {
              previousVibrations = mPreviousRingVibrations;
          } else if (vib.isNotification()) {
              previousVibrations = mPreviousNotificationVibrations;
          } else if (vib.isAlarm()) {
              previousVibrations = mPreviousAlarmVibrations;
          } else {
              previousVibrations = mPreviousVibrations;
          }
  
          if (previousVibrations.size() > mPreviousVibrationsLimit) {
              previousVibrations.removeFirst();
          }
          previousVibrations.addLast(vib.toInfo());
      }
  
      @Override // Binder call
      public void cancelVibrate(IBinder token) {
          mContext.enforceCallingOrSelfPermission(
                  android.Manifest.permission.VIBRATE,
                  "cancelVibrate");
  
          synchronized (mLock) {
              if (mCurrentVibration != null && mCurrentVibration.token == token) {
                  if (DEBUG) {
                      Slog.d(TAG, "Canceling vibration.");
                  }
                  long ident = Binder.clearCallingIdentity();
                  try {
                      doCancelVibrateLocked();
                  } finally {
                      Binder.restoreCallingIdentity(ident);
                  }
              }
          }
      }
  
      private final Runnable mVibrationEndRunnable = new Runnable() {
          @Override
          public void run() {
              onVibrationFinished();
          }
      };
  
      @GuardedBy("mLock")
      private void doCancelVibrateLocked() {
          Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
          Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
          try {
              mH.removeCallbacks(mVibrationEndRunnable);
              if (mThread != null) {
                  mThread.cancel();
                  mThread = null;
              }
              if (mCurrentExternalVibration != null) {
                  mCurrentExternalVibration.mute();
                  mCurrentExternalVibration = null;
                  setVibratorUnderExternalControl(false);
              }
              doVibratorOff();
              reportFinishVibrationLocked();
          } finally {
              Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
          }
      }
  
      // Callback for whenever the current vibration has finished played out
      public void onVibrationFinished() {
          if (DEBUG) {
              Slog.e(TAG, "Vibration finished, cleaning up");
          }
          synchronized (mLock) {
              // Make sure the vibration is really done. This also reports that the vibration is
              // finished.
              doCancelVibrateLocked();
          }
      }
  
      @GuardedBy("mLock")
      private void startVibrationLocked(final Vibration vib) {
          Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
          try {
              if (!isAllowedToVibrateLocked(vib)) {
                  return;
              }
  
              final int intensity = getCurrentIntensityLocked(vib);
              if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
                  return;
              }
  
              if (vib.isRingtone() && !shouldVibrateForRingtone()) {
                  if (DEBUG) {
                      Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
                  }
                  return;
              }
  
              final int mode = getAppOpMode(vib);
              if (mode != AppOpsManager.MODE_ALLOWED) {
                  if (mode == AppOpsManager.MODE_ERRORED) {
                      // We might be getting calls from within system_server, so we don't actually
                      // want to throw a SecurityException here.
                      Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
                  }
                  return;
              }
              applyVibrationIntensityScalingLocked(vib, intensity);
              startVibrationInnerLocked(vib);
          } finally {
              Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
          }
      }
  
      @GuardedBy("mLock")
      private void startVibrationInnerLocked(Vibration vib) {
          Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
          try {
              mCurrentVibration = vib;
              if (vib.effect instanceof VibrationEffect.OneShot) {
                  Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                  VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
                  doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib.uid, vib.attrs);
                  mH.postDelayed(mVibrationEndRunnable, oneShot.getDuration());
              } else if (vib.effect instanceof VibrationEffect.Waveform) {
                  // mThread better be null here. doCancelVibrate should always be
                  // called before startNextVibrationLocked or startVibrationLocked.
                  Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                  VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
                  mThread = new VibrateThread(waveform, vib.uid, vib.attrs);
                  mThread.start();
              } else if (vib.effect instanceof VibrationEffect.Prebaked) {
                  Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                  long timeout = doVibratorPrebakedEffectLocked(vib);
                  if (timeout > 0) {
                      mH.postDelayed(mVibrationEndRunnable, timeout);
                  }
              } else {
                  Slog.e(TAG, "Unknown vibration type, ignoring");
              }
          } finally {
              Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
          }
      }
  
      private boolean isAllowedToVibrateLocked(Vibration vib) {
          if (!mLowPowerMode) {
              return true;
          }
  
          if (vib.attrs.getUsage() == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
              return true;
          }
  
          if (vib.attrs.getUsage() == AudioAttributes.USAGE_ALARM
                  || vib.attrs.getUsage() == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY
                  || vib.attrs.getUsage()
                      == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
              return true;
          }
  
          return false;
      }
  
      private int getCurrentIntensityLocked(Vibration vib) {
          if (vib.isRingtone()) {
              return mRingIntensity;
          } else if (vib.isNotification()) {
              return mNotificationIntensity;
          } else if (vib.isHapticFeedback()) {
              return mHapticFeedbackIntensity;
          } else if (vib.isAlarm()) {
              return Vibrator.VIBRATION_INTENSITY_HIGH;
          } else {
              return Vibrator.VIBRATION_INTENSITY_MEDIUM;
          }
      }
  
      /**
       * Scale the vibration effect by the intensity as appropriate based its intent.
       */
      private void applyVibrationIntensityScalingLocked(Vibration vib, int intensity) {
          if (vib.effect instanceof VibrationEffect.Prebaked) {
              // Prebaked effects are always just a direct translation from intensity to
              // EffectStrength.
              VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked)vib.effect;
              prebaked.setEffectStrength(intensityToEffectStrength(intensity));
              return;
          }
  
          final int defaultIntensity;
          if (vib.isRingtone()) {
              defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
          } else if (vib.isNotification()) {
              defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
          } else if (vib.isHapticFeedback()) {
              defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
          } else if (vib.isAlarm()) {
              defaultIntensity = Vibrator.VIBRATION_INTENSITY_HIGH;
          } else {
              // If we don't know what kind of vibration we're playing then just skip scaling for
              // now.
              return;
          }
  
          final ScaleLevel scale = mScaleLevels.get(intensity - defaultIntensity);
          if (scale == null) {
              // We should have scaling levels for all cases, so not being able to scale because of a
              // missing level is unexpected.
              Slog.e(TAG, "No configured scaling level!"
                      + " (current=" + intensity + ", default= " + defaultIntensity + ")");
              return;
          }
  
          VibrationEffect scaledEffect = null;
          if (vib.effect instanceof VibrationEffect.OneShot) {
              VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
              oneShot = oneShot.resolve(mDefaultVibrationAmplitude);
              scaledEffect = oneShot.scale(scale.gamma, scale.maxAmplitude);
          } else if (vib.effect instanceof VibrationEffect.Waveform) {
              VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
              waveform = waveform.resolve(mDefaultVibrationAmplitude);
              scaledEffect = waveform.scale(scale.gamma, scale.maxAmplitude);
          } else {
              Slog.w(TAG, "Unable to apply intensity scaling, unknown VibrationEffect type");
          }
  
          if (scaledEffect != null) {
              vib.originalEffect = vib.effect;
              vib.effect = scaledEffect;
          }
      }
  
      private boolean shouldVibrateForRingtone() {
          AudioManager audioManager = mContext.getSystemService(AudioManager.class);
          int ringerMode = audioManager.getRingerModeInternal();
          // "Also vibrate for calls" Setting in Sound
          if (Settings.System.getInt(
                  mContext.getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING, 0) != 0) {
              return ringerMode != AudioManager.RINGER_MODE_SILENT;
          } else if (Settings.Global.getInt(
                      mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) != 0
                  && DeviceConfig.getBoolean(
                      DeviceConfig.NAMESPACE_TELEPHONY, RAMPING_RINGER_ENABLED, false)) {
              return ringerMode != AudioManager.RINGER_MODE_SILENT;
          } else {
              return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
          }
      }
  
      private static boolean shouldBypassDnd(AudioAttributes attrs) {
          return (attrs.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0;
      }
  
      private int getAppOpMode(Vibration vib) {
          int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
                  vib.attrs.getUsage(), vib.uid, vib.opPkg);
          if (mode == AppOpsManager.MODE_ALLOWED) {
              mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, vib.uid, vib.opPkg);
          }
  
          if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(vib.attrs)) {
              // If we're just ignoring the vibration op then this is set by DND and we should ignore
              // if we're asked to bypass. AppOps won't be able to record this operation, so make
              // sure we at least note it in the logs for debugging.
              Slog.d(TAG, "Bypassing DND for vibration: " + vib);
              mode = AppOpsManager.MODE_ALLOWED;
          }
          return mode;
      }
  
      @GuardedBy("mLock")
      private void reportFinishVibrationLocked() {
          Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
          try {
              if (mCurrentVibration != null) {
                  mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
                          mCurrentVibration.opPkg);
                  unlinkVibration(mCurrentVibration);
                  mCurrentVibration = null;
              }
          } finally {
              Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
          }
      }
  
      private void linkVibration(Vibration vib) {
          // Only link against waveforms since they potentially don't have a finish if
          // they're repeating. Let other effects just play out until they're done.
          if (vib.effect instanceof VibrationEffect.Waveform) {
              try {
                  vib.token.linkToDeath(vib, 0);
              } catch (RemoteException e) {
                  return;
              }
          }
      }
  
      private void unlinkVibration(Vibration vib) {
          if (vib.effect instanceof VibrationEffect.Waveform) {
              vib.token.unlinkToDeath(vib, 0);
          }
      }
  
      private void updateVibrators() {
          synchronized (mLock) {
              boolean devicesUpdated = updateInputDeviceVibratorsLocked();
              boolean lowPowerModeUpdated = updateLowPowerModeLocked();
              updateVibrationIntensityLocked();
  
              if (devicesUpdated || lowPowerModeUpdated) {
                  // If the state changes out from under us then just reset.
                  doCancelVibrateLocked();
              }
          }
      }
  
      private boolean updateInputDeviceVibratorsLocked() {
          boolean changed = false;
          boolean vibrateInputDevices = false;
          try {
              vibrateInputDevices = Settings.System.getIntForUser(
                      mContext.getContentResolver(),
                      Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
          } catch (SettingNotFoundException snfe) {
          }
          if (vibrateInputDevices != mVibrateInputDevicesSetting) {
              changed = true;
              mVibrateInputDevicesSetting = vibrateInputDevices;
          }
  
          if (mVibrateInputDevicesSetting) {
              if (!mInputDeviceListenerRegistered) {
                  mInputDeviceListenerRegistered = true;
                  mIm.registerInputDeviceListener(this, mH);
              }
          } else {
              if (mInputDeviceListenerRegistered) {
                  mInputDeviceListenerRegistered = false;
                  mIm.unregisterInputDeviceListener(this);
              }
          }
  
          mInputDeviceVibrators.clear();
          if (mVibrateInputDevicesSetting) {
              int[] ids = mIm.getInputDeviceIds();
              for (int i = 0; i < ids.length; i++) {
                  InputDevice device = mIm.getInputDevice(ids[i]);
                  Vibrator vibrator = device.getVibrator();
                  if (vibrator.hasVibrator()) {
                      mInputDeviceVibrators.add(vibrator);
                  }
              }
              return true;
          }
          return changed;
      }
  
      private boolean updateLowPowerModeLocked() {
          boolean lowPowerMode = mPowerManagerInternal
                  .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
          if (lowPowerMode != mLowPowerMode) {
              mLowPowerMode = lowPowerMode;
              return true;
          }
          return false;
      }
  
      private void updateVibrationIntensityLocked() {
          mHapticFeedbackIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
                  Settings.System.HAPTIC_FEEDBACK_INTENSITY,
                  mVibrator.getDefaultHapticFeedbackIntensity(), UserHandle.USER_CURRENT);
          mNotificationIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
                  Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
                  mVibrator.getDefaultNotificationVibrationIntensity(), UserHandle.USER_CURRENT);
          mRingIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
                  Settings.System.RING_VIBRATION_INTENSITY,
                  mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
      }
  
      @Override
      public void onInputDeviceAdded(int deviceId) {
          updateVibrators();
      }
  
      @Override
      public void onInputDeviceChanged(int deviceId) {
          updateVibrators();
      }
  
      @Override
      public void onInputDeviceRemoved(int deviceId) {
          updateVibrators();
      }
  
      private boolean doVibratorExists() {
          // For now, we choose to ignore the presence of input devices that have vibrators
          // when reporting whether the device has a vibrator.  Applications often use this
          // information to decide whether to enable certain features so they expect the
          // result of hasVibrator() to be constant.  For now, just report whether
          // the device has a built-in vibrator.
          //synchronized (mInputDeviceVibrators) {
          //    return !mInputDeviceVibrators.isEmpty() || vibratorExists();
          //}
          return vibratorExists();
      }
  
      private void doVibratorOn(long millis, int amplitude, int uid, AudioAttributes attrs) {
          Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
          try {
              synchronized (mInputDeviceVibrators) {
                  if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
                      amplitude = mDefaultVibrationAmplitude;
                  }
                  if (DEBUG) {
                      Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
                              " with amplitude " + amplitude + ".");
                  }
                  noteVibratorOnLocked(uid, millis);
                  final int vibratorCount = mInputDeviceVibrators.size();
                  if (vibratorCount != 0) {
                      for (int i = 0; i < vibratorCount; i++) {
                          mInputDeviceVibrators.get(i).vibrate(millis, attrs);
                      }
                  } else {
                      // Note: ordering is important here! Many haptic drivers will reset their
                      // amplitude when enabled, so we always have to enable frst, then set the
                      // amplitude.
                      vibratorOn(millis);
                      doVibratorSetAmplitude(amplitude);
                  }
              }
          } finally {
              Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
          }
      }
  
      private void doVibratorSetAmplitude(int amplitude) {
          if (mSupportsAmplitudeControl) {
              vibratorSetAmplitude(amplitude);
          }
      }
  
      private void doVibratorOff() {
          Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOff");
          try {
              synchronized (mInputDeviceVibrators) {
                  if (DEBUG) {
                      Slog.d(TAG, "Turning vibrator off.");
                  }
                  noteVibratorOffLocked();
                  final int vibratorCount = mInputDeviceVibrators.size();
                  if (vibratorCount != 0) {
                      for (int i = 0; i < vibratorCount; i++) {
                          mInputDeviceVibrators.get(i).cancel();
                      }
                  } else {
                      vibratorOff();
                  }
              }
          } finally {
              Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
          }
      }
  
      @GuardedBy("mLock")
      private long doVibratorPrebakedEffectLocked(Vibration vib) {
          Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked");
          try {
              final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
              final boolean usingInputDeviceVibrators;
              synchronized (mInputDeviceVibrators) {
                  usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
              }
              // Input devices don't support prebaked effect, so skip trying it with them.
              if (!usingInputDeviceVibrators) {
                  long timeout = vibratorPerformEffect(prebaked.getId(),
                          prebaked.getEffectStrength());
                  if (timeout > 0) {
                      noteVibratorOnLocked(vib.uid, timeout);
                      return timeout;
                  }
              }
              if (!prebaked.shouldFallback()) {
                  return 0;
              }
              VibrationEffect effect = getFallbackEffect(prebaked.getId());
              if (effect == null) {
                  Slog.w(TAG, "Failed to play prebaked effect, no fallback");
                  return 0;
              }
              Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid,
                      vib.opPkg, vib.reason + " (fallback)");
              final int intensity = getCurrentIntensityLocked(fallbackVib);
              linkVibration(fallbackVib);
              applyVibrationIntensityScalingLocked(fallbackVib, intensity);
              startVibrationInnerLocked(fallbackVib);
              return 0;
          } finally {
              Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
          }
      }
  
      private VibrationEffect getFallbackEffect(int effectId) {
          return mFallbackEffects.get(effectId);
      }
  
      /**
       * Return the current desired effect strength.
       *
       * If the returned value is &lt; 0 then the vibration shouldn't be played at all.
       */
      private static int intensityToEffectStrength(int intensity) {
          switch (intensity) {
              case Vibrator.VIBRATION_INTENSITY_LOW:
                  return EffectStrength.LIGHT;
              case Vibrator.VIBRATION_INTENSITY_MEDIUM:
                  return EffectStrength.MEDIUM;
              case Vibrator.VIBRATION_INTENSITY_HIGH:
                  return EffectStrength.STRONG;
              default:
                  Slog.w(TAG, "Got unexpected vibration intensity: " + intensity);
                  return EffectStrength.STRONG;
          }
      }
  
      private static boolean isNotification(int usageHint) {
          switch (usageHint) {
              case AudioAttributes.USAGE_NOTIFICATION:
              case AudioAttributes.USAGE_NOTIFICATION_EVENT:
              case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
              case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
              case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
                  return true;
              default:
                  return false;
          }
      }
  
      private static boolean isRingtone(int usageHint) {
          return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
      }
  
      private static boolean isHapticFeedback(int usageHint) {
          return usageHint == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
      }
  
      private static boolean isAlarm(int usageHint) {
          return usageHint == AudioAttributes.USAGE_ALARM;
      }
  
      private void noteVibratorOnLocked(int uid, long millis) {
          try {
              mBatteryStatsService.noteVibratorOn(uid, millis);
              StatsLog.write_non_chained(StatsLog.VIBRATOR_STATE_CHANGED, uid, null,
                      StatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, millis);
              mCurVibUid = uid;
          } catch (RemoteException e) {
          }
      }
  
      private void noteVibratorOffLocked() {
          if (mCurVibUid >= 0) {
              try {
                  mBatteryStatsService.noteVibratorOff(mCurVibUid);
                  StatsLog.write_non_chained(StatsLog.VIBRATOR_STATE_CHANGED, mCurVibUid, null,
                          StatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF, 0);
              } catch (RemoteException e) { }
              mCurVibUid = -1;
          }
      }
  
      private void setVibratorUnderExternalControl(boolean externalControl) {
          if (DEBUG) {
              if (externalControl) {
                  Slog.d(TAG, "Vibrator going under external control.");
              } else {
                  Slog.d(TAG, "Taking back control of vibrator.");
              }
          }
          mVibratorUnderExternalControl = externalControl;
          vibratorSetExternalControl(externalControl);
      }
  
      private class VibrateThread extends Thread {
          private final VibrationEffect.Waveform mWaveform;
          private final int mUid;
          private final AudioAttributes mAttrs;
  
          private boolean mForceStop;
  
          VibrateThread(VibrationEffect.Waveform waveform, int uid, AudioAttributes attrs) {
              mWaveform = waveform;
              mUid = uid;
              mAttrs = attrs;
              mTmpWorkSource.set(uid);
              mWakeLock.setWorkSource(mTmpWorkSource);
          }
  
          private long delayLocked(long duration) {
              Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "delayLocked");
              try {
                  long durationRemaining = duration;
                  if (duration > 0) {
                      final long bedtime = duration + SystemClock.uptimeMillis();
                      do {
                          try {
                              this.wait(durationRemaining);
                          }
                          catch (InterruptedException e) { }
                          if (mForceStop) {
                              break;
                          }
                          durationRemaining = bedtime - SystemClock.uptimeMillis();
                      } while (durationRemaining > 0);
                      return duration - durationRemaining;
                  }
                  return 0;
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
              }
          }
  
          public void run() {
              Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
              mWakeLock.acquire();
              try {
                  boolean finished = playWaveform();
                  if (finished) {
                      onVibrationFinished();
                  }
              } finally {
                  mWakeLock.release();
              }
          }
  
          /**
           * Play the waveform.
           *
           * @return true if it finished naturally, false otherwise (e.g. it was canceled).
           */
          public boolean playWaveform() {
              Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playWaveform");
              try {
                  synchronized (this) {
                      final long[] timings = mWaveform.getTimings();
                      final int[] amplitudes = mWaveform.getAmplitudes();
                      final int len = timings.length;
                      final int repeat = mWaveform.getRepeatIndex();
  
                      int index = 0;
                      long onDuration = 0;
                      while (!mForceStop) {
                          if (index < len) {
                              final int amplitude = amplitudes[index];
                              final long duration = timings[index++];
                              if (duration <= 0) {
                                  continue;
                              }
                              if (amplitude != 0) {
                                  if (onDuration <= 0) {
                                      // Telling the vibrator to start multiple times usually causes
                                      // effects to feel "choppy" because the motor resets at every on
                                      // command.  Instead we figure out how long our next "on" period
                                      // is going to be, tell the motor to stay on for the full
                                      // duration, and then wake up to change the amplitude at the
                                      // appropriate intervals.
                                      onDuration = getTotalOnDuration(timings, amplitudes, index - 1,
                                              repeat);
                                      doVibratorOn(onDuration, amplitude, mUid, mAttrs);
                                  } else {
                                      doVibratorSetAmplitude(amplitude);
                                  }
                              }
  
                              long waitTime = delayLocked(duration);
                              if (amplitude != 0) {
                                  onDuration -= waitTime;
                              }
                          } else if (repeat < 0) {
                              break;
                          } else {
                              index = repeat;
                          }
                      }
                      return !mForceStop;
                  }
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
              }
          }
  
          public void cancel() {
              synchronized (this) {
                  mThread.mForceStop = true;
                  mThread.notify();
              }
          }
  
          /**
           * Get the duration the vibrator will be on starting at startIndex until the next time it's
           * off.
           */
          private long getTotalOnDuration(
                  long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
              int i = startIndex;
              long timing = 0;
              while(amplitudes[i] != 0) {
                  timing += timings[i++];
                  if (i >= timings.length) {
                      if (repeatIndex >= 0) {
                          i = repeatIndex;
                          // prevent infinite loop
                          repeatIndex = -1;
                      } else {
                          break;
                      }
                  }
                  if (i == startIndex) {
                      return 1000;
                  }
              }
              return timing;
          }
      }
  
      BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                  synchronized (mLock) {
                      // When the system is entering a non-interactive state, we want
                      // to cancel vibrations in case a misbehaving app has abandoned
                      // them.  However it may happen that the system is currently playing
                      // haptic feedback as part of the transition.  So we don't cancel
                      // system vibrations.
                      if (mCurrentVibration != null
                              && !(mCurrentVibration.isHapticFeedback()
                                  && mCurrentVibration.isFromSystem())) {
                          doCancelVibrateLocked();
                      }
                  }
              }
          }
      };
  
      @Override
      protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
  
          pw.println("Vibrator Service:");
          synchronized (mLock) {
              pw.print("  mCurrentVibration=");
              if (mCurrentVibration != null) {
                  pw.println(mCurrentVibration.toInfo().toString());
              } else {
                  pw.println("null");
              }
              pw.print("  mCurrentExternalVibration=");
              if (mCurrentExternalVibration != null) {
                  pw.println(mCurrentExternalVibration.toString());
              } else {
                  pw.println("null");
              }
              pw.println("  mVibratorUnderExternalControl=" + mVibratorUnderExternalControl);
              pw.println("  mLowPowerMode=" + mLowPowerMode);
              pw.println("  mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
              pw.println("  mNotificationIntensity=" + mNotificationIntensity);
              pw.println("  mRingIntensity=" + mRingIntensity);
              pw.println("");
              pw.println("  Previous ring vibrations:");
              for (VibrationInfo info : mPreviousRingVibrations) {
                  pw.print("    ");
                  pw.println(info.toString());
              }
  
              pw.println("  Previous notification vibrations:");
              for (VibrationInfo info : mPreviousNotificationVibrations) {
                  pw.print("    ");
                  pw.println(info.toString());
              }
  
              pw.println("  Previous alarm vibrations:");
              for (VibrationInfo info : mPreviousAlarmVibrations) {
                  pw.print("    ");
                  pw.println(info.toString());
              }
  
              pw.println("  Previous vibrations:");
              for (VibrationInfo info : mPreviousVibrations) {
                  pw.print("    ");
                  pw.println(info.toString());
              }
  
              pw.println("  Previous external vibrations:");
              for (ExternalVibration vib : mPreviousExternalVibrations) {
                  pw.print("    ");
                  pw.println(vib.toString());
              }
          }
      }
  
      @Override
      public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
              String[] args, ShellCallback callback, ResultReceiver resultReceiver)
              throws RemoteException {
          new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
      }
  
      final class ExternalVibratorService extends IExternalVibratorService.Stub {
          ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
  
          @Override
          public int onExternalVibrationStart(ExternalVibration vib) {
              if (!mSupportsExternalControl) {
                  return SCALE_MUTE;
              }
              if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
                          vib.getUid(), -1 /*owningUid*/, true /*exported*/)
                      != PackageManager.PERMISSION_GRANTED) {
                  Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
                          + " tried to play externally controlled vibration"
                          + " without VIBRATE permission, ignoring.");
                  return SCALE_MUTE;
              }
  
              final int scaleLevel;
              synchronized (mLock) {
                  if (!vib.equals(mCurrentExternalVibration)) {
                      if (mCurrentExternalVibration == null) {
                          // If we're not under external control right now, then cancel any normal
                          // vibration that may be playing and ready the vibrator for external
                          // control.
                          doCancelVibrateLocked();
                          setVibratorUnderExternalControl(true);
                      }
                      // At this point we either have an externally controlled vibration playing, or
                      // no vibration playing. Since the interface defines that only one externally
                      // controlled vibration can play at a time, by returning something other than
                      // SCALE_MUTE from this function we can be assured that if we are currently
                      // playing vibration, it will be muted in favor of the new vibration.
                      //
                      // Note that this doesn't support multiple concurrent external controls, as we
                      // would need to mute the old one still if it came from a different controller.
                      mCurrentExternalVibration = vib;
                      mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
                      mCurrentExternalVibration.linkToDeath(mCurrentExternalDeathRecipient);
                      if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
                          mPreviousExternalVibrations.removeFirst();
                      }
                      mPreviousExternalVibrations.addLast(vib);
                      if (DEBUG) {
                          Slog.e(TAG, "Playing external vibration: " + vib);
                      }
                  }
                  final int usage = vib.getAudioAttributes().getUsage();
                  final int defaultIntensity;
                  final int currentIntensity;
                  if (isRingtone(usage)) {
                      defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
                      currentIntensity = mRingIntensity;
                  } else if (isNotification(usage)) {
                      defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
                      currentIntensity = mNotificationIntensity;
                  } else if (isHapticFeedback(usage)) {
                      defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
                      currentIntensity = mHapticFeedbackIntensity;
                  } else if (isAlarm(usage)) {
                      defaultIntensity = Vibrator.VIBRATION_INTENSITY_HIGH;
                      currentIntensity = Vibrator.VIBRATION_INTENSITY_HIGH;
                  } else {
                      defaultIntensity = 0;
                      currentIntensity = 0;
                  }
                  scaleLevel = currentIntensity - defaultIntensity;
              }
              if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) {
                  return scaleLevel;
              } else {
                  // Presumably we want to play this but something about our scaling has gone
                  // wrong, so just play with no scaling.
                  Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level "
                          + scaleLevel + " for vibration " + vib);
                  return SCALE_NONE;
              }
          }
  
          @Override
          public void onExternalVibrationStop(ExternalVibration vib) {
              synchronized (mLock) {
                  if (vib.equals(mCurrentExternalVibration)) {
                      mCurrentExternalVibration.unlinkToDeath(mCurrentExternalDeathRecipient);
                      mCurrentExternalDeathRecipient = null;
                      mCurrentExternalVibration = null;
                      setVibratorUnderExternalControl(false);
                      if (DEBUG) {
                          Slog.e(TAG, "Stopping external vibration" + vib);
                      }
                  }
              }
          }
  
          private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
              public void binderDied() {
                  synchronized (mLock) {
                      onExternalVibrationStop(mCurrentExternalVibration);
                  }
              }
          }
      }
  
      private final class VibratorShellCommand extends ShellCommand {
  
          private final IBinder mToken;
  
          private final class CommonOptions {
              public boolean force = false;
              public void check(String opt) {
                  switch (opt) {
                      case "-f":
                          force = true;
                          break;
                  }
              }
          }
  
          private VibratorShellCommand(IBinder token) {
              mToken = token;
          }
  
          @Override
          public int onCommand(String cmd) {
              if ("vibrate".equals(cmd)) {
                  return runVibrate();
              } else if ("waveform".equals(cmd)) {
                  return runWaveform();
              } else if ("prebaked".equals(cmd)) {
                  return runPrebaked();
              } else if ("cancel".equals(cmd)) {
                  cancelVibrate(mToken);
                  return 0;
              }
              return handleDefaultCommands(cmd);
          }
  
          private boolean checkDoNotDisturb(CommonOptions opts) {
              try {
                  final int zenMode = Settings.Global.getInt(mContext.getContentResolver(),
                          Settings.Global.ZEN_MODE);
                  if (zenMode != Settings.Global.ZEN_MODE_OFF && !opts.force) {
                      try (PrintWriter pw = getOutPrintWriter();) {
                          pw.print("Ignoring because device is on DND mode ");
                          pw.println(DebugUtils.flagsToString(Settings.Global.class, "ZEN_MODE_",
                                  zenMode));
                          return true;
                      }
                  }
              } catch (SettingNotFoundException e) {
                  // ignore
              }
  
              return false;
          }
  
          private int runVibrate() {
              Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrate");
              try {
                  CommonOptions commonOptions = new CommonOptions();
  
                  String opt;
                  while ((opt = getNextOption()) != null) {
                      commonOptions.check(opt);
                  }
  
                  if (checkDoNotDisturb(commonOptions)) {
                      return 0;
                  }
  
                  final long duration = Long.parseLong(getNextArgRequired());
                  String description = getNextArg();
                  if (description == null) {
                      description = "Shell command";
                  }
  
                  VibrationEffect effect =
                          VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
                  AudioAttributes attrs = createAudioAttributes(commonOptions);
                  vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
                          mToken);
                  return 0;
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
              }
          }
  
          private int runWaveform() {
              Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runWaveform");
              try {
                  String description = "Shell command";
                  int repeat = -1;
                  ArrayList<Integer> amplitudesList = null;
                  CommonOptions commonOptions = new CommonOptions();
  
                  String opt;
                  while ((opt = getNextOption()) != null) {
                      switch (opt) {
                          case "-d":
                              description = getNextArgRequired();
                              break;
                          case "-r":
                              repeat = Integer.parseInt(getNextArgRequired());
                              break;
                          case "-a":
                              if (amplitudesList == null) {
                                  amplitudesList = new ArrayList<Integer>();
                              }
                              break;
                          default:
                              commonOptions.check(opt);
                              break;
                      }
                  }
  
                  if (checkDoNotDisturb(commonOptions)) {
                      return 0;
                  }
  
                  ArrayList<Long> timingsList = new ArrayList<Long>();
  
                  String arg;
                  while ((arg = getNextArg()) != null) {
                      if (amplitudesList != null && amplitudesList.size() < timingsList.size()) {
                          amplitudesList.add(Integer.parseInt(arg));
                      } else {
                          timingsList.add(Long.parseLong(arg));
                      }
                  }
  
                  VibrationEffect effect;
                  long[] timings = timingsList.stream().mapToLong(Long::longValue).toArray();
                  if (amplitudesList == null) {
                      effect = VibrationEffect.createWaveform(timings, repeat);
                  } else {
                      int[] amplitudes =
                              amplitudesList.stream().mapToInt(Integer::intValue).toArray();
                      effect = VibrationEffect.createWaveform(timings, amplitudes, repeat);
                  }
                  AudioAttributes attrs = createAudioAttributes(commonOptions);
                  vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
                          mToken);
                  return 0;
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
              }
          }
  
          private int runPrebaked() {
              Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runPrebaked");
              try {
                  CommonOptions commonOptions = new CommonOptions();
  
                  String opt;
                  while ((opt = getNextOption()) != null) {
                      commonOptions.check(opt);
                  }
  
                  if (checkDoNotDisturb(commonOptions)) {
                      return 0;
                  }
  
                  final int id = Integer.parseInt(getNextArgRequired());
  
                  String description = getNextArg();
                  if (description == null) {
                      description = "Shell command";
                  }
  
                  VibrationEffect effect =
                          VibrationEffect.get(id, false);
                  AudioAttributes attrs = createAudioAttributes(commonOptions);
                  vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
                          mToken);
                  return 0;
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
              }
          }
  
          private AudioAttributes createAudioAttributes(CommonOptions commonOptions) {
              final int flags = commonOptions.force
                      ? AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
                      : 0;
              return new AudioAttributes.Builder()
                      .setUsage(AudioAttributes.USAGE_UNKNOWN)
                      .setFlags(flags)
                      .build();
          }
  
          @Override
          public void onHelp() {
              try (PrintWriter pw = getOutPrintWriter();) {
                  pw.println("Vibrator commands:");
                  pw.println("  help");
                  pw.println("    Prints this help text.");
                  pw.println("");
                  pw.println("  vibrate duration [description]");
                  pw.println("    Vibrates for duration milliseconds; ignored when device is on DND ");
                  pw.println("    (Do Not Disturb) mode.");
                  pw.println("  waveform [-d description] [-r index] [-a] duration [amplitude] ...");
                  pw.println("    Vibrates for durations and amplitudes in list;");
                  pw.println("    ignored when device is on DND (Do Not Disturb) mode.");
                  pw.println("    If -r is provided, the waveform loops back to the specified");
                  pw.println("    index (e.g. 0 loops from the beginning)");
                  pw.println("    If -a is provided, the command accepts duration-amplitude pairs;");
                  pw.println("    otherwise, it accepts durations only and alternates off/on");
                  pw.println("    Duration is in milliseconds; amplitude is a scale of 1-255.");
                  pw.println("  prebaked effect-id [description]");
                  pw.println("    Vibrates with prebaked effect; ignored when device is on DND ");
                  pw.println("    (Do Not Disturb) mode.");
                  pw.println("  cancel");
                  pw.println("    Cancels any active vibration");
                  pw.println("Common Options:");
                  pw.println("  -f - Force. Ignore Do Not Disturb setting.");
                  pw.println("");
              }
          }
      }
  
  }
  