Extract input device vibrators code from VibratorService

This code will also be migrated to VibratorManagerService, to give
preference to input devices over the devices vibrators when the user
setting enables it.

This need to remain a server-side code so permissions/setting will be
applied to system vibrations even when they are redirected to input
devices (including vibration scaling).

Bug: 167946816
Bug: 131311651
Test: atest FrameworksServiceTests:VibratorServiceTest
      atest FrameworksServiceTests:InputDeviceVibratorsTest
Change-Id: I6c61c3f20e4d82d44af136917d3755d9f6edd31f
This commit is contained in:
Lais Andrade
2020-10-22 13:15:09 +00:00
parent 2902a86853
commit e320282bba
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);
}
}