Merge "Implement vibrate and cancelVibrate on VibratorManagerService" into sc-dev

This commit is contained in:
Lais Andrade
2021-02-02 17:15:44 +00:00
committed by Android (Google) Code Review
8 changed files with 1012 additions and 44 deletions

View File

@@ -364,8 +364,22 @@ public abstract class CombinedVibrationEffect implements Parcelable {
@Override
public long getDuration() {
long maxDuration = Long.MIN_VALUE;
boolean hasUnknownStep = false;
for (int i = 0; i < mEffects.size(); i++) {
maxDuration = Math.max(maxDuration, mEffects.valueAt(i).getDuration());
long duration = mEffects.valueAt(i).getDuration();
if (duration == Long.MAX_VALUE) {
// If any duration is repeating, this combination duration is also repeating.
return duration;
}
maxDuration = Math.max(maxDuration, duration);
// If any step is unknown, this combination duration will also be unknown, unless
// any step is repeating. Repeating vibrations take precedence over non-repeating
// ones in the service, so continue looping to check for repeating steps.
hasUnknownStep |= duration < 0;
}
if (hasUnknownStep) {
// If any step is unknown, this combination duration is also unknown.
return -1;
}
return maxDuration;
}
@@ -477,16 +491,25 @@ public abstract class CombinedVibrationEffect implements Parcelable {
@Override
public long getDuration() {
boolean hasUnknownStep = false;
long durations = 0;
final int effectCount = mEffects.size();
for (int i = 0; i < effectCount; i++) {
CombinedVibrationEffect effect = mEffects.get(i);
long duration = effect.getDuration();
if (duration < 0) {
// If any duration is unknown, this combination duration is also unknown.
if (duration == Long.MAX_VALUE) {
// If any duration is repeating, this combination duration is also repeating.
return duration;
}
durations += duration;
// If any step is unknown, this combination duration will also be unknown, unless
// any step is repeating. Repeating vibrations take precedence over non-repeating
// ones in the service, so continue looping to check for repeating steps.
hasUnknownStep |= duration < 0;
}
if (hasUnknownStep) {
// If any step is unknown, this combination duration is also unknown.
return -1;
}
long delays = 0;
for (int i = 0; i < effectCount; i++) {

View File

@@ -17,6 +17,7 @@
package android.os;
import android.os.CombinedVibrationEffect;
import android.os.IVibratorStateListener;
import android.os.VibrationAttributes;
import android.os.VibratorInfo;
@@ -24,6 +25,9 @@ import android.os.VibratorInfo;
interface IVibratorManagerService {
int[] getVibratorIds();
VibratorInfo getVibratorInfo(int vibratorId);
boolean isVibrating(int vibratorId);
boolean registerVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
in CombinedVibrationEffect effect, in VibrationAttributes attributes);
void vibrate(int uid, String opPkg, in CombinedVibrationEffect effect,

View File

@@ -116,6 +116,61 @@ public class CombinedVibrationEffectTest {
() -> CombinedVibrationEffect.startSequential().combine());
}
@Test
public void testDurationMono() {
assertEquals(1, CombinedVibrationEffect.createSynced(
VibrationEffect.createOneShot(1, 1)).getDuration());
assertEquals(-1, CombinedVibrationEffect.createSynced(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).getDuration());
assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.createSynced(
VibrationEffect.createWaveform(
new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0)).getDuration());
}
@Test
public void testDurationStereo() {
assertEquals(6, CombinedVibrationEffect.startSynced()
.addVibrator(1, VibrationEffect.createOneShot(1, 1))
.addVibrator(2,
VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
.combine()
.getDuration());
assertEquals(-1, CombinedVibrationEffect.startSynced()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addVibrator(2,
VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
.combine()
.getDuration());
assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSynced()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addVibrator(2,
VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
.combine()
.getDuration());
}
@Test
public void testDurationSequential() {
assertEquals(26, CombinedVibrationEffect.startSequential()
.addNext(1, VibrationEffect.createOneShot(10, 10), 10)
.addNext(2,
VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
.combine()
.getDuration());
assertEquals(-1, CombinedVibrationEffect.startSequential()
.addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addNext(2,
VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
.combine()
.getDuration());
assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSequential()
.addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addNext(2,
VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
.combine()
.getDuration());
}
@Test
public void testSerializationMono() {
CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);

View File

@@ -22,12 +22,20 @@ import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.vibrator.IVibrator;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.CombinedVibrationEffect;
import android.os.ExternalVibration;
import android.os.Handler;
import android.os.IBinder;
import android.os.IExternalVibratorService;
import android.os.IVibratorManagerService;
import android.os.IVibratorStateListener;
import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.Trace;
@@ -40,9 +48,12 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.server.vibrator.InputDeviceDelegate;
import com.android.server.vibrator.Vibration;
import com.android.server.vibrator.VibrationScaler;
import com.android.server.vibrator.VibrationSettings;
import com.android.server.vibrator.VibrationThread;
import com.android.server.vibrator.VibratorController;
import libcore.util.NativeAllocationRegistry;
@@ -51,6 +62,8 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -83,18 +96,31 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
// Used to generate globally unique vibration ids.
private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
private final Object mLock = new Object();
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
private final Handler mHandler;
private final AppOpsManager mAppOps;
private final NativeWrapper mNativeWrapper;
private final int[] mVibratorIds;
private final SparseArray<VibratorController> mVibrators;
private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
@GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
@GuardedBy("mLock")
private VibrationThread mCurrentVibration;
@GuardedBy("mLock")
private VibrationThread mNextVibration;
@GuardedBy("mLock")
private ExternalVibrationHolder mCurrentExternalVibration;
private VibrationSettings mVibrationSettings;
private VibrationScaler mVibrationScaler;
private InputDeviceDelegate mInputDeviceDelegate;
static native long nativeInit();
@@ -109,8 +135,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mNativeWrapper = injector.getNativeWrapper();
mNativeWrapper.init();
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
mAppOps = mContext.getSystemService(AppOpsManager.class);
PowerManager pm = context.getSystemService(PowerManager.class);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
int[] vibratorIds = mNativeWrapper.getVibratorIds();
if (vibratorIds == null) {
mVibratorIds = new int[0];
@@ -134,6 +167,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
try {
mVibrationSettings = new VibrationSettings(mContext, mHandler);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
mVibrationSettings.addListener(this::updateServiceState);
@@ -144,6 +178,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
@Override // Binder call
public int[] getVibratorIds() {
return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
}
@Override // Binder call
@Nullable
public VibratorInfo getVibratorInfo(int vibratorId) {
@@ -152,8 +191,37 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override // Binder call
public int[] getVibratorIds() {
return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
public boolean isVibrating(int vibratorId) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_VIBRATOR_STATE,
"isVibrating");
VibratorController controller = mVibrators.get(vibratorId);
return controller != null && controller.isVibrating();
}
@Override // Binder call
public boolean registerVibratorStateListener(int vibratorId, IVibratorStateListener listener) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_VIBRATOR_STATE,
"registerVibratorStateListener");
VibratorController controller = mVibrators.get(vibratorId);
if (controller == null) {
return false;
}
return controller.registerVibratorStateListener(listener);
}
@Override // Binder call
public boolean unregisterVibratorStateListener(int vibratorId,
IVibratorStateListener listener) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_VIBRATOR_STATE,
"unregisterVibratorStateListener");
VibratorController controller = mVibrators.get(vibratorId);
if (controller == null) {
return false;
}
return controller.unregisterVibratorStateListener(listener);
}
@Override // Binder call
@@ -161,9 +229,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attrs) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setAlwaysOnEffect");
try {
if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.VIBRATE_ALWAYS_ON,
"setAlwaysOnEffect");
if (effect == null) {
synchronized (mLock) {
mAlwaysOnEffects.delete(alwaysOnId);
@@ -200,12 +269,89 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@Override // Binder call
public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
@Nullable VibrationAttributes attrs, String reason, IBinder token) {
throw new UnsupportedOperationException("Not implemented");
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
try {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");
if (token == null) {
Slog.e(TAG, "token must not be null");
return;
}
enforceUpdateAppOpsStatsPermission(uid);
if (!isEffectValid(effect)) {
return;
}
effect = fixupVibrationEffect(effect);
attrs = fixupVibrationAttributes(attrs);
Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
uid, opPkg, reason);
synchronized (mLock) {
Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
if (ignoreStatus != null) {
endVibrationLocked(vib, ignoreStatus);
return;
}
VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
mBatteryStatsService, mVibrationCallbacks);
ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread);
if (ignoreStatus != null) {
endVibrationLocked(vib, ignoreStatus);
return;
}
final long ident = Binder.clearCallingIdentity();
try {
if (mCurrentVibration != null) {
mCurrentVibration.cancel();
}
Vibration.Status status = startVibrationLocked(vibThread);
if (status != Vibration.Status.RUNNING) {
endVibrationLocked(vib, status);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
@Override // Binder call
public void cancelVibrate(IBinder token) {
throw new UnsupportedOperationException("Not implemented");
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.VIBRATE,
"cancelVibrate");
synchronized (mLock) {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration.");
}
final long ident = Binder.clearCallingIdentity();
try {
mNextVibration = null;
if (mCurrentVibration != null
&& mCurrentVibration.getVibration().token == token) {
mCurrentVibration.cancel();
}
if (mCurrentExternalVibration != null) {
// TODO(b/167946816): end vibration and add to list to be dumped for debug
mCurrentExternalVibration.externalVibration.mute();
mCurrentExternalVibration = null;
// TODO(b/167946816): set external control to false
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
@Override
@@ -214,11 +360,24 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver);
}
private void updateServiceState() {
@VisibleForTesting
void updateServiceState() {
synchronized (mLock) {
boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
mVibrationSettings.shouldVibrateInputDevices());
for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
updateAlwaysOnLocked(mAlwaysOnEffects.valueAt(i));
}
if (mCurrentVibration == null) {
return;
}
if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
mCurrentVibration.getVibration().attrs.getUsage())) {
mCurrentVibration.cancel();
}
}
}
@@ -230,9 +389,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (vibrator == null) {
continue;
}
Vibration.Status ignoredStatus = shouldIgnoreVibrationLocked(
Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
vib.uid, vib.opPkg, vib.attrs);
if (ignoredStatus == null) {
if (ignoreStatus == null) {
effect = mVibrationScaler.scale(effect, vib.attrs.getUsage());
} else {
// Vibration should not run, use null effect to remove registered effect.
@@ -242,6 +401,134 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
@GuardedBy("mLock")
private Vibration.Status startVibrationLocked(VibrationThread vibThread) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
Vibration vib = vibThread.getVibration();
vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
if (inputDevicesAvailable) {
return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
}
if (mCurrentVibration == null) {
return startVibrationThreadLocked(vibThread);
}
mNextVibration = vibThread;
return Vibration.Status.RUNNING;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
@GuardedBy("mLock")
private Vibration.Status startVibrationThreadLocked(VibrationThread vibThread) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
try {
Vibration vib = vibThread.getVibration();
int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
mCurrentVibration = vibThread;
mCurrentVibration.start();
return Vibration.Status.RUNNING;
case AppOpsManager.MODE_ERRORED:
Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid);
return Vibration.Status.IGNORED_ERROR_APP_OPS;
default:
return Vibration.Status.IGNORED_APP_OPS;
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
@GuardedBy("mLock")
private void endVibrationLocked(Vibration vib, Vibration.Status status) {
// TODO(b/167946816): end vibration and add to list to be dumped for debug
}
@GuardedBy("mLock")
private void reportFinishedVibrationLocked(Vibration.Status status) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
try {
Vibration vib = mCurrentVibration.getVibration();
mCurrentVibration = null;
endVibrationLocked(vib, status);
finishAppOpModeLocked(vib.uid, vib.opPkg);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
private void onVibrationComplete(int vibratorId, long vibrationId) {
synchronized (mLock) {
if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) {
if (DEBUG) {
Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+ " complete, notifying thread");
}
mCurrentVibration.vibratorComplete(vibratorId);
}
}
}
/**
* Check if given vibration should be ignored in favour of one of the vibrations currently
* running on the same vibrators.
*
* @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
*/
@GuardedBy("mLock")
@Nullable
private Vibration.Status shouldIgnoreVibrationForCurrentLocked(VibrationThread vibThread) {
if (vibThread.getVibration().isRepeating()) {
// Repeating vibrations always take precedence.
return null;
}
if (mCurrentVibration != null && mCurrentVibration.getVibration().isRepeating()) {
if (DEBUG) {
Slog.d(TAG, "Ignoring incoming vibration in favor of previous alarm vibration");
}
return Vibration.Status.IGNORED_FOR_ALARM;
}
return null;
}
/**
* Check if given vibration should be ignored by this service.
*
* @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
* @see #shouldIgnoreVibrationLocked(int, String, VibrationAttributes)
*/
@GuardedBy("mLock")
@Nullable
private Vibration.Status shouldIgnoreVibrationLocked(Vibration vib) {
// If something has external control of the vibrator, assume that it's more important.
if (mCurrentExternalVibration != null) {
if (DEBUG) {
Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
}
return Vibration.Status.IGNORED_FOR_EXTERNAL;
}
if (!mVibrationSettings.shouldVibrateForUid(vib.uid, vib.attrs.getUsage())) {
Slog.e(TAG, "Ignoring incoming vibration as process with"
+ " uid= " + vib.uid + " is background,"
+ " attrs= " + vib.attrs);
return Vibration.Status.IGNORED_BACKGROUND;
}
return shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
}
/**
* Check if a vibration with given {@code uid}, {@code opPkg} and {@code attrs} should be
* ignored by this service.
@@ -271,7 +558,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return Vibration.Status.IGNORED_RINGTONE;
}
int mode = getAppOpMode(uid, opPkg, attrs);
int mode = checkAppOpModeLocked(uid, opPkg, attrs);
if (mode != AppOpsManager.MODE_ALLOWED) {
if (mode == AppOpsManager.MODE_ERRORED) {
// We might be getting calls from within system_server, so we don't actually
@@ -290,21 +577,49 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
* Check which mode should be set for a vibration with given {@code uid}, {@code opPkg} and
* {@code attrs}. This will return one of the AppOpsManager.MODE_*.
*/
private int getAppOpMode(int uid, String opPkg, VibrationAttributes attrs) {
@GuardedBy("mLock")
private int checkAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
attrs.getAudioUsage(), uid, opPkg);
if (mode == AppOpsManager.MODE_ALLOWED) {
mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg);
}
if (mode == AppOpsManager.MODE_IGNORED
&& attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
int fixedMode = fixupAppOpModeLocked(mode, attrs);
if (mode != fixedMode && fixedMode == AppOpsManager.MODE_ALLOWED) {
// 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 vibrate from uid " + uid);
mode = AppOpsManager.MODE_ALLOWED;
}
return mode;
return fixedMode;
}
/** Start an operation in {@link AppOpsManager}, if allowed. */
@GuardedBy("mLock")
private int startAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
return fixupAppOpModeLocked(
mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg), attrs);
}
/**
* Finish a previously started operation in {@link AppOpsManager}. This will be a noop if no
* operation with same uid was previously started.
*/
@GuardedBy("mLock")
private void finishAppOpModeLocked(int uid, String opPkg) {
mAppOps.finishOp(AppOpsManager.OP_VIBRATE, uid, opPkg);
}
/**
* Enforces {@link android.Manifest.permission#UPDATE_APP_OPS_STATS} to incoming UID if it's
* different from the calling UID.
*/
private void enforceUpdateAppOpsStatsPermission(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);
}
/**
@@ -329,6 +644,49 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return true;
}
/**
* Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
* VibrationSettings#getFallbackEffect}.
*/
private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) {
if (effect instanceof CombinedVibrationEffect.Mono) {
return CombinedVibrationEffect.createSynced(
fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect()));
} else if (effect instanceof CombinedVibrationEffect.Stereo) {
CombinedVibrationEffect.SyncedCombination combination =
CombinedVibrationEffect.startSynced();
SparseArray<VibrationEffect> effects =
((CombinedVibrationEffect.Stereo) effect).getEffects();
for (int i = 0; i < effects.size(); i++) {
combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i)));
}
return combination.combine();
} else if (effect instanceof CombinedVibrationEffect.Sequential) {
CombinedVibrationEffect.SequentialCombination combination =
CombinedVibrationEffect.startSequential();
List<CombinedVibrationEffect> effects =
((CombinedVibrationEffect.Sequential) effect).getEffects();
for (CombinedVibrationEffect e : effects) {
combination.addNext(fixupVibrationEffect(e));
}
return combination.combine();
}
return effect;
}
private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
if (effect instanceof VibrationEffect.Prebaked
&& ((VibrationEffect.Prebaked) effect).shouldFallback()) {
VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
if (fallback != null) {
return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
fallback);
}
}
return effect;
}
/**
* Return new {@link VibrationAttributes} that only applies flags that this user has permissions
* to use.
@@ -388,6 +746,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
/**
* Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to
* allow bypassing {@link AppOpsManager} checks.
*/
@GuardedBy("mLock")
private int fixupAppOpModeLocked(int mode, VibrationAttributes attrs) {
if (mode == AppOpsManager.MODE_IGNORED
&& attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
return AppOpsManager.MODE_ALLOWED;
}
return mode;
}
private boolean hasPermission(String permission) {
return mContext.checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
@@ -427,6 +798,42 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
/**
* Implementation of {@link VibrationThread.VibrationCallbacks} that controls synced vibrations
* and reports them when finished.
*/
private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
@Override
public void prepareSyncedVibration(int requiredCapabilities, int[] vibratorIds) {
// TODO(b/167946816): call IVibratorManager to prepare
}
@Override
public void triggerSyncedVibration(long vibrationId) {
// TODO(b/167946816): call IVibratorManager to trigger
}
@Override
public void onVibrationEnded(long vibrationId, Vibration.Status status) {
if (DEBUG) {
Slog.d(TAG, "Vibration " + vibrationId + " thread finished with status " + status);
}
synchronized (mLock) {
if (mCurrentVibration != null
&& mCurrentVibration.getVibration().id == vibrationId) {
reportFinishedVibrationLocked(status);
if (mNextVibration != null) {
VibrationThread vibThread = mNextVibration;
mNextVibration = null;
startVibrationThreadLocked(vibThread);
}
}
}
}
}
/**
* Implementation of {@link VibratorController.OnVibrationCompleteListener} with a weak
* reference to this service.
@@ -443,7 +850,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
public void onComplete(int vibratorId, long vibrationId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
// TODO(b/159207608): finish vibration if all vibrators finished for this vibration
service.onVibrationComplete(vibratorId, vibrationId);
}
}
}
@@ -469,6 +876,41 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
/** Holder for a {@link ExternalVibration}. */
private final class ExternalVibrationHolder {
public final ExternalVibration externalVibration;
public int scale;
private final long mStartTimeDebug;
private long mEndTimeDebug;
private Vibration.Status mStatus;
private ExternalVibrationHolder(ExternalVibration externalVibration) {
this.externalVibration = externalVibration;
this.scale = IExternalVibratorService.SCALE_NONE;
mStartTimeDebug = System.currentTimeMillis();
mStatus = Vibration.Status.RUNNING;
}
public void end(Vibration.Status status) {
if (mStatus != Vibration.Status.RUNNING) {
// Vibration already ended, keep first ending status set and ignore this one.
return;
}
mStatus = status;
mEndTimeDebug = System.currentTimeMillis();
}
public Vibration.DebugInfo getDebugInfo() {
return new Vibration.DebugInfo(
mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
scale, externalVibration.getVibrationAttributes(),
externalVibration.getUid(), externalVibration.getPackage(),
/* reason= */ null, mStatus);
}
}
/** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
@VisibleForTesting
public static class NativeWrapper {

View File

@@ -138,6 +138,11 @@ public class Vibration {
return mStatus != Status.RUNNING;
}
/** Return true is effect is a repeating vibration. */
public boolean isRepeating() {
return mEffect.getDuration() == Long.MAX_VALUE;
}
/** Return the effect that should be played by this vibration. */
@Nullable
public CombinedVibrationEffect getEffect() {

View File

@@ -114,10 +114,6 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
}
}
public Vibration getVibration() {
return mVibration;
}
@Override
public void binderDied() {
cancel();
@@ -156,6 +152,10 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
}
}
public Vibration getVibration() {
return mVibration;
}
@VisibleForTesting
SparseArray<VibratorController> getVibrators() {
return mVibrators;

View File

@@ -16,34 +16,61 @@
package com.android.server;
import static com.android.server.testutils.TestUtils.assertExpectException;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManagerInternal;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.hardware.vibrator.IVibrator;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.os.CombinedVibrationEffect;
import android.os.Handler;
import android.os.IBinder;
import android.os.IVibratorStateListener;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.vibrator.FakeVibrator;
import com.android.server.vibrator.FakeVibratorControllerProvider;
import com.android.server.vibrator.VibratorController;
@@ -51,12 +78,16 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
/**
* Tests for {@link VibratorManagerService}.
@@ -67,39 +98,86 @@ import java.util.Map;
@Presubmit
public class VibratorManagerServiceTest {
private static final int TEST_TIMEOUT_MILLIS = 1_000;
private static final int UID = Process.ROOT_UID;
private static final String PACKAGE_NAME = "package";
private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
.setBatterySaverEnabled(true).build();
private static final VibrationAttributes ALARM_ATTRS =
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_TOUCH).build();
private static final VibrationAttributes NOTIFICATION_ATTRS =
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_NOTIFICATION).build();
private static final VibrationAttributes RINGTONE_ATTRS =
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_RINGTONE).build();
@Rule public MockitoRule rule = MockitoJUnit.rule();
@Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock;
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private PowerManagerInternal mPowerManagerInternalMock;
@Mock private PowerSaveState mPowerSaveStateMock;
@Mock private AppOpsManager mAppOpsManagerMock;
@Mock private IInputManager mIInputManagerMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private Context mContextSpy;
private TestLooper mTestLooper;
private FakeVibrator mVibrator;
private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
mVibrator = new FakeVibrator();
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibrator);
when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
.thenReturn(new ComponentName("", ""));
when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION))
.thenReturn(mPowerSaveStateMock);
doAnswer(invocation -> {
mRegisteredPowerModeListener = invocation.getArgument(0);
return null;
}).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_MEDIUM);
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_MEDIUM);
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
Vibrator.VIBRATION_INTENSITY_MEDIUM);
addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
mTestLooper.startAutoDispatch();
}
@After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.removeServiceForTest(PowerManagerInternal.class);
}
private VibratorManagerService createService() {
VibratorManagerService service = new VibratorManagerService(
InstrumentationRegistry.getContext(),
mContextSpy,
new VibratorManagerService.Injector() {
@Override
VibratorManagerService.NativeWrapper getNativeWrapper() {
@@ -171,6 +249,74 @@ public class VibratorManagerServiceTest {
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
}
@Test
public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
mockVibrators(1);
VibratorManagerService service = createService();
IVibratorStateListener listenerMock = mockVibratorStateListener();
service.registerVibratorStateListener(1, listenerMock);
vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
// Wait until service knows vibrator is on.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
// Wait until effect ends.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
InOrder inOrderVerifier = inOrder(listenerMock);
// First notification done when listener is registered.
inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
inOrderVerifier.verifyNoMoreInteractions();
}
@Test
public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
mockVibrators(1);
VibratorManagerService service = createService();
IVibratorStateListener listenerMock = mockVibratorStateListener();
service.registerVibratorStateListener(1, listenerMock);
vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
// Wait until service knows vibrator is on.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
service.unregisterVibratorStateListener(1, listenerMock);
// Wait until vibrator is off.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
InOrder inOrderVerifier = inOrder(listenerMock);
// First notification done when listener is registered.
inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
inOrderVerifier.verify(listenerMock, atLeastOnce()).asBinder(); // unregister
inOrderVerifier.verifyNoMoreInteractions();
}
@Test
public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception {
mockVibrators(0, 1, 2);
VibratorManagerService service = createService();
IVibratorStateListener[] listeners = new IVibratorStateListener[3];
for (int i = 0; i < 3; i++) {
listeners[i] = mockVibratorStateListener();
service.registerVibratorStateListener(i, listeners[i]);
}
vibrate(service, CombinedVibrationEffect.startSynced()
.addVibrator(0, VibrationEffect.createOneShot(40, 100))
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.combine(), ALARM_ATTRS);
// Wait until service knows vibrator is on.
assertTrue(waitUntil(s -> s.isVibrating(0), service, TEST_TIMEOUT_MILLIS));
verify(listeners[0]).onVibrating(eq(true));
verify(listeners[1]).onVibrating(eq(true));
verify(listeners[2], never()).onVibrating(eq(true));
}
@Test
public void setAlwaysOnEffect_withMono_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
mockVibrators(1, 2, 3);
@@ -276,22 +422,264 @@ public class VibratorManagerServiceTest {
}
@Test
public void vibrate_isUnsupported() {
public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
mockVibrators(1);
mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
VibratorManagerService service = createService();
CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
assertExpectException(UnsupportedOperationException.class,
"Not implemented",
() -> service.vibrate(UID, PACKAGE_NAME, effect, ALARM_ATTRS, "reason", service));
vibrate(service, VibrationEffect.createOneShot(40, 1), RINGTONE_ATTRS);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
service = createService();
vibrate(service, VibrationEffect.createOneShot(40, 10), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
service = createService();
vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
assertEquals(2, mVibratorProviders.get(1).getEffects().size());
assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes());
}
@Test
public void cancelVibrate_isUnsupported() {
public void vibrate_withPowerMode_usesPowerModeState() throws Exception {
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createService();
CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
assertExpectException(UnsupportedOperationException.class,
"Not implemented", () -> service.cancelVibrate(service));
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
service, TEST_TIMEOUT_MILLIS));
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
service, TEST_TIMEOUT_MILLIS));
assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes());
}
@Test
public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
VibratorManagerService service = createService();
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
audioAttributes, effect).build();
vibrate(service, effect, vibrationAttributes);
verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
}
@Test
public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
VibratorManagerService service = createService();
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_UNKNOWN).build());
InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
anyInt(), anyString());
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
}
@Test
public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
VibratorManagerService service = createService();
// Prebaked vibration will play fallback waveform on input device.
ArgumentCaptor<VibrationEffect> captor = ArgumentCaptor.forClass(VibrationEffect.class);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
verify(mIInputManagerMock).vibrate(eq(1), captor.capture(), any());
assertTrue(captor.getValue() instanceof VibrationEffect.Waveform);
VibrationEffect[] effects = new VibrationEffect[]{
VibrationEffect.createOneShot(100, 128),
VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose(),
};
for (VibrationEffect effect : effects) {
vibrate(service, effect, ALARM_ATTRS);
verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
}
// VibrationThread will start this vibration async, so wait before checking it never played.
assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
service, /* timeout= */ 50));
}
@Test
public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createService();
// The native callback will be dispatched manually in this test.
mTestLooper.stopAutoDispatchAndIgnoreExceptions();
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait before triggering callbacks.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
// Trigger callbacks from controller.
mTestLooper.moveTimeForward(50);
mTestLooper.dispatchAll();
// VibrationThread needs some time to react to native callbacks and stop the vibrator.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
}
@Test
public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
mVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_HIGH);
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
Vibrator.VIBRATION_INTENSITY_LOW);
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_OFF);
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_EFFECTS);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createService();
vibrate(service, CombinedVibrationEffect.startSynced()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.combine(), ALARM_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, CombinedVibrationEffect.startSequential()
.addNext(1, VibrationEffect.createOneShot(20, 100))
.combine(), NOTIFICATION_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose(), HAPTIC_FEEDBACK_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
assertEquals(3, fakeVibrator.getEffects().size());
assertEquals(1, fakeVibrator.getAmplitudes().size());
// Alarm vibration is always VIBRATION_INTENSITY_HIGH.
VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false,
VibrationEffect.EFFECT_STRENGTH_STRONG);
assertEquals(expected, fakeVibrator.getEffects().get(0));
// Notification vibrations will be scaled with SCALE_VERY_HIGH.
assertTrue(150 < fakeVibrator.getAmplitudes().get(0));
// Haptic feedback vibrations will be scaled with SCALE_LOW.
VibrationEffect.Composed played =
(VibrationEffect.Composed) fakeVibrator.getEffects().get(2);
assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale);
assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale);
// Ring vibrations have intensity OFF and are not played.
}
@Test
public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
mockVibrators(1, 2);
VibratorManagerService service = createService();
vibrate(service,
CombinedVibrationEffect.startSynced()
.addVibrator(1, VibrationEffect.createOneShot(1000, 100))
.combine(),
HAPTIC_FEEDBACK_ATTRS);
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
// Haptic feedback cancelled on low power mode.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
}
@Test
public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception {
mockVibrators(1);
VibratorManagerService service = createService();
vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
service.updateServiceState();
// Vibration is not stopped nearly after updating service.
assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
}
@Test
public void cancelVibrate_stopsVibrating() throws Exception {
mockVibrators(1);
VibratorManagerService service = createService();
service.cancelVibrate(service);
assertFalse(service.isVibrating(1));
vibrate(service, VibrationEffect.createOneShot(10_000, 100), ALARM_ATTRS);
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
service.cancelVibrate(service);
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
}
private void mockVibrators(int... vibratorIds) {
@@ -302,8 +690,56 @@ public class VibratorManagerServiceTest {
when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
}
private IVibratorStateListener mockVibratorStateListener() {
IVibratorStateListener listenerMock = mock(IVibratorStateListener.class);
IBinder binderMock = mock(IBinder.class);
when(listenerMock.asBinder()).thenReturn(binderMock);
return listenerMock;
}
private InputDevice createInputDeviceWithVibrator(int id) {
return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
null, /* hasVibrator= */ true, false, false, false, false);
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
LocalServices.removeServiceForTest(clazz);
LocalServices.addService(clazz, mock);
}
private void setRingerMode(int ringerMode) {
AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
audioManager.setRingerModeInternal(ringerMode);
assertEquals(ringerMode, audioManager.getRingerModeInternal());
}
private void setUserSetting(String settingName, int value) {
Settings.System.putIntForUser(
mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
}
private void setGlobalSetting(String settingName, int value) {
Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
}
private void vibrate(VibratorManagerService service, VibrationEffect effect,
VibrationAttributes attrs) {
vibrate(service, CombinedVibrationEffect.createSynced(effect), attrs);
}
private void vibrate(VibratorManagerService service, CombinedVibrationEffect effect,
VibrationAttributes attrs) {
service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
}
private boolean waitUntil(Predicate<VibratorManagerService> predicate,
VibratorManagerService service, long timeout) throws InterruptedException {
long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
boolean predicateResult = false;
while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
Thread.sleep(10);
predicateResult = predicate.test(service);
}
return predicateResult;
}
}

View File

@@ -582,16 +582,19 @@ public class VibratorServiceTest {
VibratorService service = createService();
service.registerVibratorStateListener(mVibratorStateListenerMock);
verify(mVibratorStateListenerMock).onVibrating(false);
vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait before triggering callbacks.
Thread.sleep(10);
assertTrue(service.isVibrating());
service.unregisterVibratorStateListener(mVibratorStateListenerMock);
// Trigger callbacks from controller.
mTestLooper.moveTimeForward(150);
mTestLooper.moveTimeForward(50);
mTestLooper.dispatchAll();
Thread.sleep(20);
assertFalse(service.isVibrating());
InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
// First notification done when listener is registered.