Merge "Implement vibrate and cancelVibrate on VibratorManagerService" into sc-dev
This commit is contained in:
@@ -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++) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user