Merge "Extract input device vibrators code from VibratorService"

This commit is contained in:
TreeHugger Robot
2020-11-26 11:48:18 +00:00
committed by Android (Google) Code Review
6 changed files with 653 additions and 126 deletions

View File

@@ -56,6 +56,7 @@ import android.view.PointerIcon;
import android.view.VerifiedInputEvent;
import android.view.WindowManager.LayoutParams;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
@@ -268,6 +269,21 @@ public final class InputManager {
mIm = im;
}
/**
* Gets an instance of the input manager.
*
* @return The input manager instance.
*
* @hide
*/
@VisibleForTesting
public static InputManager resetInstance(IInputManager inputManagerService) {
synchronized (InputManager.class) {
sInstance = new InputManager(inputManagerService);
return sInstance;
}
}
/**
* Gets an instance of the input manager.
*
@@ -1428,7 +1444,7 @@ public final class InputManager {
@Override
public boolean hasAmplitudeControl() {
return false;
return true;
}
/**

View File

@@ -29,6 +29,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Vibrator;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -423,9 +425,13 @@ public final class InputDevice implements Parcelable {
}
};
// Called by native code.
/**
* Called by native code
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
@VisibleForTesting
public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone,
boolean hasButtonUnderPad) {

View File

@@ -29,7 +29,6 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.hardware.input.InputManager;
import android.hardware.vibrator.IVibrator;
import android.os.BatteryStats;
import android.os.Binder;
@@ -60,13 +59,13 @@ import android.os.WorkSource;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.vibrator.InputDeviceDelegate;
import com.android.server.vibrator.VibrationScaler;
import com.android.server.vibrator.VibrationSettings;
@@ -82,15 +81,15 @@ import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class VibratorService extends IVibratorService.Stub
implements InputManager.InputDeviceListener {
/** System implementation of {@link IVibratorService}. */
public class VibratorService extends IVibratorService.Stub {
private static final String TAG = "VibratorService";
private static final SimpleDateFormat DEBUG_DATE_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
private static final boolean DEBUG = false;
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = {0, 30, 100, 30};
// Default vibration attributes. Used when vibration is requested without attributes
private static final VibrationAttributes DEFAULT_ATTRIBUTES =
@@ -122,19 +121,13 @@ public class VibratorService extends IVibratorService.Stub
private final IBatteryStats mBatteryStatsService;
private final String mSystemUiPackage;
private PowerManagerInternal mPowerManagerInternal;
private InputManager mIm;
private VibrationSettings mVibrationSettings;
private VibrationScaler mVibrationScaler;
private InputDeviceDelegate mInputDeviceDelegate;
private final NativeWrapper mNativeWrapper;
private volatile VibrateWaveformThread mThread;
// mInputDeviceVibrators lock should be acquired after mLock, if both are
// to be acquired
private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<>();
private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
@GuardedBy("mLock")
private Vibration mCurrentVibration;
private int mCurVibUid = -1;
@@ -343,6 +336,7 @@ public class VibratorService extends IVibratorService.Stub
public enum Status {
RUNNING,
FINISHED,
FORWARDED_TO_INPUT_DEVICES,
CANCELLED,
ERROR_APP_OPS,
IGNORED,
@@ -577,9 +571,9 @@ public class VibratorService extends IVibratorService.Stub
public void systemReady() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady");
try {
mIm = mContext.getSystemService(InputManager.class);
mVibrationSettings = new VibrationSettings(mContext, mH);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mPowerManagerInternal.registerLowPowerModeObserver(
@@ -707,12 +701,8 @@ public class VibratorService extends IVibratorService.Stub
@Override // Binder call
public boolean hasAmplitudeControl() {
synchronized (mInputDeviceVibrators) {
// Input device vibrators don't support amplitude controls yet, but are still used over
// the system vibrator when connected.
return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)
&& mInputDeviceVibrators.isEmpty();
}
// Input device vibrators always support amplitude controls.
return mInputDeviceDelegate.isAvailable() || hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
}
@Override // Binder call
@@ -1059,10 +1049,8 @@ public class VibratorService extends IVibratorService.Stub
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
doVibratorOn(vib);
} else if (vib.effect instanceof VibrationEffect.Waveform) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
mThread = new VibrateWaveformThread(vib);
mThread.start();
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
doVibratorWaveformEffectLocked(vib);
} else if (vib.effect instanceof VibrationEffect.Prebaked) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
doVibratorPrebakedEffectLocked(vib);
@@ -1193,7 +1181,8 @@ public class VibratorService extends IVibratorService.Stub
private void updateVibrators() {
synchronized (mLock) {
boolean devicesUpdated = updateInputDeviceVibratorsLocked();
boolean devicesUpdated = mInputDeviceDelegate.updateInputDeviceVibrators(
mVibrationSettings.shouldVibrateInputDevices());
boolean lowPowerModeUpdated = updateLowPowerModeLocked();
if (devicesUpdated || lowPowerModeUpdated) {
@@ -1205,41 +1194,6 @@ public class VibratorService extends IVibratorService.Stub
}
}
private boolean updateInputDeviceVibratorsLocked() {
boolean changed = false;
boolean vibrateInputDevices = mVibrationSettings.shouldVibrateInputDevices();
if (vibrateInputDevices != mVibrateInputDevicesSetting) {
changed = true;
mVibrateInputDevicesSetting = vibrateInputDevices;
}
if (mVibrateInputDevicesSetting) {
if (!mInputDeviceListenerRegistered) {
mInputDeviceListenerRegistered = true;
mIm.registerInputDeviceListener(this, mH);
}
} else {
if (mInputDeviceListenerRegistered) {
mInputDeviceListenerRegistered = false;
mIm.unregisterInputDeviceListener(this);
}
}
mInputDeviceVibrators.clear();
if (mVibrateInputDevicesSetting) {
int[] ids = mIm.getInputDeviceIds();
for (int i = 0; i < ids.length; i++) {
InputDevice device = mIm.getInputDevice(ids[i]);
Vibrator vibrator = device.getVibrator();
if (vibrator.hasVibrator()) {
mInputDeviceVibrators.add(vibrator);
}
}
return true;
}
return changed;
}
private boolean updateLowPowerModeLocked() {
boolean lowPowerMode = mPowerManagerInternal
.getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
@@ -1268,57 +1222,36 @@ public class VibratorService extends IVibratorService.Stub
}
}
@Override
public void onInputDeviceAdded(int deviceId) {
updateVibrators();
}
@Override
public void onInputDeviceChanged(int deviceId) {
updateVibrators();
}
@Override
public void onInputDeviceRemoved(int deviceId) {
updateVibrators();
}
private boolean doVibratorExists() {
// For now, we choose to ignore the presence of input devices that have vibrators
// when reporting whether the device has a vibrator. Applications often use this
// information to decide whether to enable certain features so they expect the
// result of hasVibrator() to be constant. For now, just report whether
// the device has a built-in vibrator.
//synchronized (mInputDeviceVibrators) {
// return !mInputDeviceVibrators.isEmpty() || vibratorExists();
//}
return mNativeWrapper.vibratorExists();
}
private void doVibratorOn(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
try {
synchronized (mInputDeviceVibrators) {
final VibrationEffect.OneShot oneShot = vib.effect.resolve(
mDefaultVibrationAmplitude);
if (DEBUG) {
Slog.d(TAG, "Turning vibrator on for " + oneShot.getDuration() + " ms"
+ " with amplitude " + oneShot.getAmplitude() + ".");
}
final VibrationEffect.OneShot oneShot = vib.effect.resolve(mDefaultVibrationAmplitude);
if (DEBUG) {
Slog.d(TAG, "Turning vibrator on for " + oneShot.getDuration() + " ms"
+ " with amplitude " + oneShot.getAmplitude() + ".");
}
boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
vib.uid, vib.opPkg, oneShot, vib.reason, vib.attrs);
if (inputDevicesAvailable) {
// The set current vibration is no longer being played by this service, so drop it.
mCurrentVibration = null;
endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES);
} else {
noteVibratorOnLocked(vib.uid, oneShot.getDuration());
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
mInputDeviceVibrators.get(i).vibrate(vib.uid, vib.opPkg, oneShot,
vib.reason, vib.attrs);
}
} else {
// Note: ordering is important here! Many haptic drivers will reset their
// amplitude when enabled, so we always have to enable first, then set the
// amplitude.
mNativeWrapper.vibratorOn(oneShot.getDuration(), vib.id);
doVibratorSetAmplitude(oneShot.getAmplitude());
}
// Note: ordering is important here! Many haptic drivers will reset their
// amplitude when enabled, so we always have to enable first, then set the
// amplitude.
mNativeWrapper.vibratorOn(oneShot.getDuration(), vib.id);
doVibratorSetAmplitude(oneShot.getAmplitude());
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -1334,19 +1267,34 @@ public class VibratorService extends IVibratorService.Stub
private void doVibratorOff() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOff");
try {
synchronized (mInputDeviceVibrators) {
if (DEBUG) {
Slog.d(TAG, "Turning vibrator off.");
}
noteVibratorOffLocked();
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
mInputDeviceVibrators.get(i).cancel();
}
} else {
mNativeWrapper.vibratorOff();
}
if (DEBUG) {
Slog.d(TAG, "Turning vibrator off.");
}
noteVibratorOffLocked();
boolean inputDevicesAvailable = mInputDeviceDelegate.cancelVibrateIfAvailable();
if (!inputDevicesAvailable) {
mNativeWrapper.vibratorOff();
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
@GuardedBy("mLock")
private void doVibratorWaveformEffectLocked(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorWaveformEffectLocked");
try {
boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
vib.uid, vib.opPkg, vib.effect, vib.reason, vib.attrs);
if (inputDevicesAvailable) {
// The set current vibration is no longer being played by this service, so drop it.
mCurrentVibration = null;
endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES);
} else {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
mThread = new VibrateWaveformThread(vib);
mThread.start();
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -1358,12 +1306,9 @@ public class VibratorService extends IVibratorService.Stub
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked");
try {
final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
final boolean usingInputDeviceVibrators;
synchronized (mInputDeviceVibrators) {
usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
}
// Input devices don't support prebaked effect, so skip trying it with them.
if (!usingInputDeviceVibrators) {
// Input devices don't support prebaked effect, so skip trying it with them and allow
// fallback to be attempted.
if (!mInputDeviceDelegate.isAvailable()) {
long duration = mNativeWrapper.vibratorPerformEffect(
prebaked.getId(), prebaked.getEffectStrength(), vib.id);
if (duration > 0) {
@@ -1401,15 +1346,17 @@ public class VibratorService extends IVibratorService.Stub
try {
final VibrationEffect.Composed composed = (VibrationEffect.Composed) vib.effect;
final boolean usingInputDeviceVibrators;
synchronized (mInputDeviceVibrators) {
usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
}
// Input devices don't support composed effect, so skip trying it with them.
if (usingInputDeviceVibrators || !hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED);
boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
vib.uid, vib.opPkg, composed, vib.reason, vib.attrs);
if (inputDevicesAvailable) {
// The set current vibration is no longer being played by this service, so drop it.
mCurrentVibration = null;
endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES);
return;
} else if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
// The set current vibration is not actually playing, so drop it.
mCurrentVibration = null;
endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED);
return;
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.vibrator;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.SparseArray;
import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
/** Delegates vibrations to all connected {@link InputDevice} with available {@link Vibrator}. */
// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
public final class InputDeviceDelegate implements InputManager.InputDeviceListener {
private static final String TAG = "InputDeviceDelegate";
private final Object mLock = new Object();
private final Handler mHandler;
private final InputManager mInputManager;
@GuardedBy("mLock")
private final SparseArray<Vibrator> mInputDeviceVibrators = new SparseArray<>();
/**
* Flag updated via {@link #updateInputDeviceVibrators(boolean)}, holding the value of {@link
* android.provider.Settings.System#VIBRATE_INPUT_DEVICES}.
*/
@GuardedBy("mLock")
private boolean mShouldVibrateInputDevices;
public InputDeviceDelegate(Context context, Handler handler) {
mHandler = handler;
mInputManager = context.getSystemService(InputManager.class);
}
@Override
public void onInputDeviceAdded(int deviceId) {
updateInputDevice(deviceId);
}
@Override
public void onInputDeviceChanged(int deviceId) {
updateInputDevice(deviceId);
}
@Override
public void onInputDeviceRemoved(int deviceId) {
synchronized (mLock) {
mInputDeviceVibrators.remove(deviceId);
}
}
/**
* Return {@code true} is there are input devices with vibrators available and vibrations should
* be delegated to them.
*/
public boolean isAvailable() {
synchronized (mLock) {
// mInputDeviceVibrators is cleared when settings are disabled, so this check is enough.
return mInputDeviceVibrators.size() > 0;
}
}
/**
* Vibrate all {@link InputDevice} with {@link Vibrator} available using given effect.
*
* @return {@link #isAvailable()}
*/
public boolean vibrateIfAvailable(int uid, String opPkg, VibrationEffect effect,
String reason, VibrationAttributes attrs) {
synchronized (mLock) {
for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs);
}
return mInputDeviceVibrators.size() > 0;
}
}
/**
* Cancel vibration on all {@link InputDevice} with {@link Vibrator} available.
*
* @return {@link #isAvailable()}
*/
public boolean cancelVibrateIfAvailable() {
synchronized (mLock) {
for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
mInputDeviceVibrators.valueAt(i).cancel();
}
return mInputDeviceVibrators.size() > 0;
}
}
/**
* Updates the list of {@link InputDevice} vibrators based on the {@link
* VibrationSettings#shouldVibrateInputDevices()} setting current value and the
* devices currently available in {@link InputManager#getInputDeviceIds()}.
*
* @return true if there was any change in input devices available or related settings.
*/
public boolean updateInputDeviceVibrators(boolean vibrateInputDevices) {
synchronized (mLock) {
if (vibrateInputDevices == mShouldVibrateInputDevices) {
// No need to update if settings haven't changed.
return false;
}
mShouldVibrateInputDevices = vibrateInputDevices;
mInputDeviceVibrators.clear();
if (vibrateInputDevices) {
// Register the listener first so any device added/updated/removed after the call to
// getInputDeviceIds() will trigger the callbacks (which will wait on the lock for
// this loop to finish).
mInputManager.registerInputDeviceListener(this, mHandler);
for (int deviceId : mInputManager.getInputDeviceIds()) {
InputDevice device = mInputManager.getInputDevice(deviceId);
if (device == null) {
continue;
}
Vibrator vibrator = device.getVibrator();
if (vibrator.hasVibrator()) {
mInputDeviceVibrators.put(device.getId(), vibrator);
}
}
} else {
mInputManager.unregisterInputDeviceListener(this);
}
}
return true;
}
private void updateInputDevice(int deviceId) {
synchronized (mLock) {
if (!mShouldVibrateInputDevices) {
// No need to keep this device vibrator if setting is off.
return;
}
InputDevice device = mInputManager.getInputDevice(deviceId);
if (device == null) {
mInputDeviceVibrators.remove(deviceId);
return;
}
Vibrator vibrator = device.getVibrator();
if (vibrator.hasVibrator()) {
mInputDeviceVibrators.put(deviceId, vibrator);
} else {
mInputDeviceVibrators.remove(deviceId);
}
}
}
}

View File

@@ -43,6 +43,8 @@ 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;
@@ -63,6 +65,7 @@ import android.os.Vibrator;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
@@ -120,6 +123,7 @@ public class VibratorServiceTest {
@Mock private AppOpsManager mAppOpsManagerMock;
@Mock private VibratorService.NativeWrapper mNativeWrapperMock;
@Mock private IVibratorStateListener mVibratorStateListenerMock;
@Mock private IInputManager mIInputManagerMock;
@Mock private IBinder mVibratorStateListenerBinderMock;
private TestLooper mTestLooper;
@@ -129,10 +133,12 @@ public class VibratorServiceTest {
public void setUp() throws Exception {
mTestLooper = new TestLooper();
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(mVibratorMock);
when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
when(mVibratorMock.getDefaultHapticFeedbackIntensity())
.thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
@@ -145,6 +151,7 @@ public class VibratorServiceTest {
.thenReturn(new ComponentName("", ""));
when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION))
.thenReturn(mPowerSaveStateMock);
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
@@ -216,6 +223,15 @@ public class VibratorServiceTest {
assertFalse(createService().hasAmplitudeControl());
}
@Test
public void hasAmplitudeControl_withInputDevices_returnsTrue() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
assertTrue(createService().hasAmplitudeControl());
}
@Test
public void areEffectsSupported_withNullResultFromNative_returnsSupportUnknown() {
when(mNativeWrapperMock.vibratorGetSupportedEffects()).thenReturn(null);
@@ -374,6 +390,22 @@ public class VibratorServiceTest {
eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
}
@Test
public void vibrate_withOneShotAndInputDevices_vibratesInputDevices() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
VibrationEffect effect = VibrationEffect.createOneShot(100, 128);
vibrate(service, effect);
assertFalse(service.isVibrating());
verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
verify(mNativeWrapperMock, never()).vibratorOn(anyLong(), anyLong());
}
@Test
public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude() {
mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -415,6 +447,25 @@ public class VibratorServiceTest {
eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG), gt(0L));
}
@Test
public void vibrate_withPrebakedAndInputDevices_vibratesFallbackWaveformOnInputDevices()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
assertFalse(service.isVibrating());
// Wait for VibrateThread to turn input device vibrator ON.
Thread.sleep(5);
verify(mIInputManagerMock).vibrate(eq(1), any(), any());
verify(mNativeWrapperMock, never()).vibratorOn(anyLong(), anyLong());
verify(mNativeWrapperMock, never()).vibratorPerformEffect(anyLong(), anyLong(), anyLong());
}
@Test
public void vibrate_withComposed_performsEffect() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
@@ -441,6 +492,27 @@ public class VibratorServiceTest {
assertEquals(10, primitive.delay);
}
@Test
public void vibrate_withComposedAndInputDevices_vibratesInputDevices()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
when(mIInputManagerMock.getInputDevice(2)).thenReturn(createInputDeviceWithVibrator(2));
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
.compose();
vibrate(service, effect);
assertFalse(service.isVibrating());
verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
verify(mIInputManagerMock).vibrate(eq(2), eq(effect), any());
verify(mNativeWrapperMock, never()).vibratorPerformComposedEffect(any(), anyLong());
}
@Test
public void vibrate_withWaveform_controlsVibratorAmplitudeDuringTotalVibrationTime()
throws Exception {
@@ -499,6 +571,25 @@ public class VibratorServiceTest {
delay < maxDelay);
}
@Test
public void vibrate_withWaveformAndInputDevices_vibratesInputDevices() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
vibrate(service, effect);
assertFalse(service.isVibrating());
// Wait for VibrateThread to turn input device vibrator ON.
Thread.sleep(5);
verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
verify(mNativeWrapperMock, never()).vibratorOn(anyLong(), anyLong());
}
@Test
public void vibrate_withOneShotAndNativeCallbackTriggered_finishesVibration() {
VibratorService service = createService();
@@ -795,6 +886,11 @@ public class VibratorServiceTest {
when(mNativeWrapperMock.vibratorGetCapabilities()).thenReturn((long) capabilities);
}
private InputDevice createInputDeviceWithVibrator(int id) {
return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
null, /* hasVibrator= */ true, false, false);
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
LocalServices.removeServiceForTest(clazz);
LocalServices.addService(clazz, mock);

View File

@@ -0,0 +1,291 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.vibrator;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Process;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
/**
* Tests for {@link InputDeviceDelegate}.
*
* Build/Install/Run:
* atest FrameworksServicesTests:InputDeviceDelegateTest
*/
@Presubmit
public class InputDeviceDelegateTest {
private static final int UID = Process.ROOT_UID;
private static final String PACKAGE_NAME = "package";
private static final String REASON = "some reason";
private static final VibrationAttributes VIBRATION_ATTRIBUTES =
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
@Rule public MockitoRule rule = MockitoJUnit.rule();
@Mock private IInputManager mIInputManagerMock;
private TestLooper mTestLooper;
private ContextWrapper mContextSpy;
private InputDeviceDelegate mInputDeviceDelegate;
private IInputDevicesChangedListener mIInputDevicesChangedListener;
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
doAnswer(invocation -> mIInputDevicesChangedListener = invocation.getArgument(0))
.when(mIInputManagerMock).registerInputDevicesChangedListener(any());
mInputDeviceDelegate = new InputDeviceDelegate(
mContextSpy, new Handler(mTestLooper.getLooper()));
}
@Test
public void onInputDeviceAdded_withSettingsDisabled_ignoresNewDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
assertFalse(mInputDeviceDelegate.isAvailable());
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
mInputDeviceDelegate.onInputDeviceAdded(1);
assertFalse(mInputDeviceDelegate.isAvailable());
verify(mIInputManagerMock, never()).getInputDevice(anyInt());
}
@Test
public void onInputDeviceAdded_withDeviceWithoutVibrator_ignoresNewDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertFalse(mInputDeviceDelegate.isAvailable());
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithoutVibrator(1));
updateInputDevices(new int[]{1});
assertFalse(mInputDeviceDelegate.isAvailable());
verify(mIInputManagerMock).getInputDevice(eq(1));
}
@Test
public void onInputDeviceAdded_withDeviceWithVibrator_addsNewDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertFalse(mInputDeviceDelegate.isAvailable());
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
updateInputDevices(new int[]{1});
assertTrue(mInputDeviceDelegate.isAvailable());
verify(mIInputManagerMock).getInputDevice(eq(1));
}
@Test
public void onInputDeviceChanged_withSettingsDisabled_ignoresDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
updateInputDevices(new int[]{1});
assertFalse(mInputDeviceDelegate.isAvailable());
verify(mIInputManagerMock, never()).getInputDevice(anyInt());
}
@Test
public void onInputDeviceChanged_deviceLosesVibrator_removesDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithVibrator(1), createInputDeviceWithoutVibrator(1));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertTrue(mInputDeviceDelegate.isAvailable());
updateInputDevices(new int[]{1});
assertFalse(mInputDeviceDelegate.isAvailable());
verify(mIInputManagerMock, times(2)).getInputDevice(eq(1));
}
@Test
public void onInputDeviceChanged_deviceLost_removesDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithVibrator(1), (InputDevice) null);
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertTrue(mInputDeviceDelegate.isAvailable());
updateInputDevices(new int[]{1});
assertFalse(mInputDeviceDelegate.isAvailable());
verify(mIInputManagerMock, times(2)).getInputDevice(eq(1));
}
@Test
public void onInputDeviceChanged_deviceAddsVibrator_addsDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithoutVibrator(1), createInputDeviceWithVibrator(1));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertFalse(mInputDeviceDelegate.isAvailable());
updateInputDevices(new int[]{1});
assertTrue(mInputDeviceDelegate.isAvailable());
verify(mIInputManagerMock, times(2)).getInputDevice(eq(1));
}
@Test
public void onInputDeviceRemoved_removesDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(
createInputDeviceWithoutVibrator(1));
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertTrue(mInputDeviceDelegate.isAvailable());
updateInputDevices(new int[]{1});
assertFalse(mInputDeviceDelegate.isAvailable());
}
@Test
public void updateInputDeviceVibrators_usesFlagToLoadDeviceList() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertTrue(mInputDeviceDelegate.isAvailable());
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
assertFalse(mInputDeviceDelegate.isAvailable());
}
@Test
public void updateInputDeviceVibrators_withDeviceWithoutVibrator_deviceIsIgnored()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithoutVibrator(1));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertFalse(mInputDeviceDelegate.isAvailable());
}
@Test
public void vibrateIfAvailable_withNoInputDevice_returnsFalse() {
VibrationEffect effect = VibrationEffect.createOneShot(100, 255);
assertFalse(mInputDeviceDelegate.isAvailable());
assertFalse(mInputDeviceDelegate.vibrateIfAvailable(
UID, PACKAGE_NAME, effect, REASON, VIBRATION_ATTRIBUTES));
}
@Test
public void vibrateIfAvailable_withInputDevices_returnsTrueAndVibratesAllDevices()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
VibrationEffect effect = VibrationEffect.createOneShot(100, 255);
assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
UID, PACKAGE_NAME, effect, REASON, VIBRATION_ATTRIBUTES));
verify(mIInputManagerMock).vibrate(eq(1), same(effect), any());
verify(mIInputManagerMock).vibrate(eq(2), same(effect), any());
}
@Test
public void cancelVibrateIfAvailable_withNoInputDevice_returnsFalse() throws Exception {
assertFalse(mInputDeviceDelegate.isAvailable());
assertFalse(mInputDeviceDelegate.cancelVibrateIfAvailable());
verify(mIInputManagerMock, never()).cancelVibrate(anyInt(), any());
}
@Test
public void cancelVibrateIfAvailable_withInputDevices_returnsTrueAndStopsAllDevices()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertTrue(mInputDeviceDelegate.isAvailable());
assertTrue(mInputDeviceDelegate.cancelVibrateIfAvailable());
verify(mIInputManagerMock).cancelVibrate(eq(1), any());
verify(mIInputManagerMock).cancelVibrate(eq(2), any());
}
private void updateInputDevices(int[] deviceIds) throws Exception {
int[] deviceIdsAndGenerations = new int[deviceIds.length * 2];
for (int i = 0; i < deviceIdsAndGenerations.length; i += 2) {
deviceIdsAndGenerations[i] = deviceIds[i / 2];
deviceIdsAndGenerations[i + 1] = 2; // update by increasing it's generation to 2.
}
// Force initialization of mIInputDevicesChangedListener, if it still haven't
InputManager.getInstance().getInputDeviceIds();
mIInputDevicesChangedListener.onInputDevicesChanged(deviceIdsAndGenerations);
// Makes sure all callbacks from InputDeviceDelegate are executed.
mTestLooper.dispatchAll();
}
private InputDevice createInputDeviceWithVibrator(int id) {
return createInputDevice(id, /* hasVibrator= */ true);
}
private InputDevice createInputDeviceWithoutVibrator(int id) {
return createInputDevice(id, /* hasVibrator= */ false);
}
private InputDevice createInputDevice(int id, boolean hasVibrator) {
return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
null, hasVibrator, false, false);
}
}