blob: 8b79b9ddfef22b8fe59d669dd306a4d91430b011 [file] [log] [blame]
/*
* 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.app.AppOpsManager;
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.Constants.EffectStrength;
import android.media.AudioManager;
import android.os.PowerSaveState;
import android.os.BatteryStats;
import android.os.Handler;
import android.os.IVibratorService;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.IBinder;
import android.os.Binder;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.Vibrator;
import android.os.VibrationEffect;
import android.os.WorkSource;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.DebugUtils;
import android.util.Slog;
import android.view.InputDevice;
import android.media.AudioAttributes;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.server.power.BatterySaverPolicy.ServiceType;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
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 final LinkedList<VibrationInfo> mPreviousVibrations;
private final int mPreviousVibrationsLimit;
private final boolean mAllowPriorityVibrationsInLowPowerMode;
private final boolean mSupportsAmplitudeControl;
private final int mDefaultVibrationAmplitude;
private final VibrationEffect[] mFallbackEffects;
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 IAppOpsService mAppOpsService;
private final IBatteryStats mBatteryStatsService;
private PowerManagerInternal mPowerManagerInternal;
private InputManager mIm;
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
private Vibration mCurrentVibration;
private int mCurVibUid = -1;
private boolean mLowPowerMode;
private SettingsObserver mSettingObserver;
native static boolean vibratorExists();
native static void vibratorInit();
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
native static boolean vibratorSupportsAmplitudeControl();
native static void vibratorSetAmplitude(int amplitude);
native static long vibratorPerformEffect(long effect, long strength);
private class Vibration implements IBinder.DeathRecipient {
private final IBinder mToken;
private final VibrationEffect mEffect;
private final long mStartTime;
private final int mUsageHint;
private final int mUid;
private final String mOpPkg;
private Vibration(IBinder token, VibrationEffect effect,
int usageHint, int uid, String opPkg) {
mToken = token;
mEffect = effect;
mStartTime = SystemClock.uptimeMillis();
mUsageHint = usageHint;
mUid = uid;
mOpPkg = opPkg;
}
public void binderDied() {
synchronized (mLock) {
if (this == mCurrentVibration) {
doCancelVibrateLocked();
}
}
}
public boolean hasLongerTimeout(long millis) {
// If the current effect is a one shot vibration that will end after the given timeout
// for the new one shot vibration, then just let the current vibration finish. All
// other effect types will get pre-empted.
if (mEffect instanceof VibrationEffect.OneShot) {
VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) mEffect;
return mStartTime + oneShot.getTiming() > SystemClock.uptimeMillis() + millis;
}
return false;
}
public boolean isSystemHapticFeedback() {
boolean repeating = false;
if (mEffect instanceof VibrationEffect.Waveform) {
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) mEffect;
repeating = (waveform.getRepeatIndex() < 0);
}
return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
&& !repeating;
}
}
private static class VibrationInfo {
private final long mStartTime;
private final VibrationEffect mEffect;
private final int mUsageHint;
private final int mUid;
private final String mOpPkg;
public VibrationInfo(long startTime, VibrationEffect effect,
int usageHint, int uid, String opPkg) {
mStartTime = startTime;
mEffect = effect;
mUsageHint = usageHint;
mUid = uid;
mOpPkg = opPkg;
}
@Override
public String toString() {
return new StringBuilder()
.append(", startTime: ")
.append(mStartTime)
.append(", effect: ")
.append(mEffect)
.append(", usageHint: ")
.append(mUsageHint)
.append(", uid: ")
.append(mUid)
.append(", opPkg: ")
.append(mOpPkg)
.toString();
}
}
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();
mContext = context;
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
mAppOpsService =
IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
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);
mPreviousVibrations = new LinkedList<>();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(mIntentReceiver, filter);
long[] clickEffectTimings = getLongIntArray(context.getResources(),
com.android.internal.R.array.config_virtualKeyVibePattern);
VibrationEffect clickEffect = createEffect(clickEffectTimings);
VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
new long[] {0, 30, 100, 30} /*timings*/, -1);
long[] tickEffectTimings = getLongIntArray(context.getResources(),
com.android.internal.R.array.config_clockTickVibePattern);
VibrationEffect tickEffect = createEffect(tickEffectTimings);
mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect, tickEffect };
}
private static VibrationEffect createEffect(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() {
mIm = mContext.getSystemService(InputManager.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.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateVibrators();
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
updateVibrators();
}
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, int usageHint,
IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
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 our current vibration is longer than the new vibration and is the same amplitude,
// then just let the current one finish.
if (effect instanceof VibrationEffect.OneShot
&& mCurrentVibration != null
&& mCurrentVibration.mEffect instanceof VibrationEffect.OneShot) {
VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
VibrationEffect.OneShot currentOneShot =
(VibrationEffect.OneShot) mCurrentVibration.mEffect;
if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
&& newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
if (DEBUG) {
Slog.d(TAG, "Ignoring incoming vibration in favor of current 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.mEffect)) {
if (DEBUG) {
Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
}
return;
}
Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
// 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 (effect instanceof VibrationEffect.Waveform) {
try {
token.linkToDeath(vib, 0);
} catch (RemoteException e) {
return;
}
}
long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
doCancelVibrateLocked();
startVibrationLocked(vib);
addToPreviousVibrationsLocked(vib);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private static boolean isRepeatingVibration(VibrationEffect effect) {
if (effect instanceof VibrationEffect.Waveform) {
final VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
if (waveform.getRepeatIndex() >= 0) {
return true;
}
}
return false;
}
private void addToPreviousVibrationsLocked(Vibration vib) {
if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
mPreviousVibrations.removeFirst();
}
mPreviousVibrations.addLast(new VibrationInfo(
vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg));
}
@Override // Binder call
public void cancelVibrate(IBinder token) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.VIBRATE,
"cancelVibrate");
synchronized (mLock) {
if (mCurrentVibration != null && mCurrentVibration.mToken == 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();
}
};
private void doCancelVibrateLocked() {
mH.removeCallbacks(mVibrationEndRunnable);
if (mThread != null) {
mThread.cancel();
mThread = null;
}
doVibratorOff();
reportFinishVibrationLocked();
}
// 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();
}
}
private void startVibrationLocked(final Vibration vib) {
if (!isAllowedToVibrate(vib)) {
if (DEBUG) {
Slog.e(TAG, "Vibrate ignored, low power mode");
}
return;
}
if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
!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.mUid);
}
return;
}
startVibrationInnerLocked(vib);
}
private void startVibrationInnerLocked(Vibration vib) {
mCurrentVibration = vib;
if (vib.mEffect instanceof VibrationEffect.OneShot) {
VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.mEffect;
doVibratorOn(oneShot.getTiming(), oneShot.getAmplitude(), vib.mUid, vib.mUsageHint);
mH.postDelayed(mVibrationEndRunnable, oneShot.getTiming());
} else if (vib.mEffect instanceof VibrationEffect.Waveform) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.mEffect;
mThread = new VibrateThread(waveform, vib.mUid, vib.mUsageHint);
mThread.start();
} else if (vib.mEffect instanceof VibrationEffect.Prebaked) {
long timeout = doVibratorPrebakedEffectLocked(vib);
if (timeout > 0) {
mH.postDelayed(mVibrationEndRunnable, timeout);
}
} else {
Slog.e(TAG, "Unknown vibration type, ignoring");
}
}
private boolean isAllowedToVibrate(Vibration vib) {
if (!mLowPowerMode) {
return true;
}
if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
return true;
}
if (!mAllowPriorityVibrationsInLowPowerMode) {
return false;
}
if (vib.mUsageHint == AudioAttributes.USAGE_ALARM ||
vib.mUsageHint == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY ||
vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
return true;
}
return false;
}
private boolean shouldVibrateForRingtone() {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
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 {
return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
}
}
private int getAppOpMode(Vibration vib) {
int mode;
try {
mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
vib.mUsageHint, vib.mUid, vib.mOpPkg);
if (mode == AppOpsManager.MODE_ALLOWED) {
mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get appop mode for vibration!", e);
mode = AppOpsManager.MODE_IGNORED;
}
return mode;
}
private void reportFinishVibrationLocked() {
if (mCurrentVibration != null) {
try {
mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
mCurrentVibration.mOpPkg);
} catch (RemoteException e) { }
mCurrentVibration = null;
}
}
private void unlinkVibration(Vibration vib) {
if (vib.mEffect instanceof VibrationEffect.Waveform) {
vib.mToken.unlinkToDeath(vib, 0);
}
}
private void updateVibrators() {
synchronized (mLock) {
boolean devicesUpdated = updateInputDeviceVibratorsLocked();
boolean lowPowerModeUpdated = updateLowPowerModeLocked();
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;
}
@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, int usageHint) {
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) {
final AudioAttributes attributes =
new AudioAttributes.Builder().setUsage(usageHint).build();
for (int i = 0; i < vibratorCount; i++) {
mInputDeviceVibrators.get(i).vibrate(millis, attributes);
}
} 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);
}
}
}
private void doVibratorSetAmplitude(int amplitude) {
if (mSupportsAmplitudeControl) {
vibratorSetAmplitude(amplitude);
}
}
private void doVibratorOff() {
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();
}
}
}
private long doVibratorPrebakedEffectLocked(Vibration vib) {
synchronized (mInputDeviceVibrators) {
VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
// Input devices don't support prebaked effect, so skip trying it with them.
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount == 0) {
long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
if (timeout > 0) {
noteVibratorOnLocked(vib.mUid, timeout);
return timeout;
}
}
if (!prebaked.shouldFallback()) {
return 0;
}
final int id = prebaked.getId();
if (id < 0 || id >= mFallbackEffects.length || mFallbackEffects[id] == null) {
Slog.w(TAG, "Failed to play prebaked effect, no fallback");
return 0;
}
VibrationEffect effect = mFallbackEffects[id];
Vibration fallbackVib =
new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
startVibrationInnerLocked(fallbackVib);
}
return 0;
}
private void noteVibratorOnLocked(int uid, long millis) {
try {
mBatteryStatsService.noteVibratorOn(uid, millis);
mCurVibUid = uid;
} catch (RemoteException e) {
}
}
private void noteVibratorOffLocked() {
if (mCurVibUid >= 0) {
try {
mBatteryStatsService.noteVibratorOff(mCurVibUid);
} catch (RemoteException e) { }
mCurVibUid = -1;
}
}
private class VibrateThread extends Thread {
private final VibrationEffect.Waveform mWaveform;
private final int mUid;
private final int mUsageHint;
private boolean mForceStop;
VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
mWaveform = waveform;
mUid = uid;
mUsageHint = usageHint;
mTmpWorkSource.set(uid);
mWakeLock.setWorkSource(mTmpWorkSource);
}
private long delayLocked(long duration) {
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;
}
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() {
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, mUsageHint);
} else {
doVibratorSetAmplitude(amplitude);
}
}
long waitTime = delayLocked(duration);
if (amplitude != 0) {
onDuration -= waitTime;
}
} else if (repeat < 0) {
break;
} else {
index = repeat;
}
}
return !mForceStop;
}
}
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;
} 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.isSystemHapticFeedback()) {
doCancelVibrateLocked();
}
}
}
}
};
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
pw.println("Previous vibrations:");
synchronized (mLock) {
for (VibrationInfo info : mPreviousVibrations) {
pw.print(" ");
pw.println(info.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);
}
private final class VibratorShellCommand extends ShellCommand {
private static final long MAX_VIBRATION_MS = 200;
private final IBinder mToken;
private VibratorShellCommand(IBinder token) {
mToken = token;
}
@Override
public int onCommand(String cmd) {
if ("vibrate".equals(cmd)) {
return runVibrate();
}
return handleDefaultCommands(cmd);
}
private int runVibrate() {
try {
final int zenMode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ZEN_MODE);
if (zenMode != Settings.Global.ZEN_MODE_OFF) {
try (PrintWriter pw = getOutPrintWriter();) {
pw.print("Ignoring because device is on DND mode ");
pw.println(DebugUtils.flagsToString(Settings.Global.class, "ZEN_MODE_",
zenMode));
return 0;
}
}
} catch (SettingNotFoundException e) {
// ignore
}
final long duration = Long.parseLong(getNextArgRequired());
if (duration > MAX_VIBRATION_MS) {
throw new IllegalArgumentException("maximum duration is " + MAX_VIBRATION_MS);
}
String description = getNextArg();
if (description == null) {
description = "Shell command";
}
VibrationEffect effect =
VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
mToken);
return 0;
}
@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("");
}
}
}
}