From 41e60546b08280cf8fa49283718a6fe63d0d21cc Mon Sep 17 00:00:00 2001 From: Lais Andrade Date: Thu, 25 Jun 2020 10:51:52 +0000 Subject: [PATCH] Create VibratorServiceTest Add basic tests to cover main interaction between VibratorService.java and it's native methods, which will be changed after vibrator HAL controller is integrated. Bug: 153418251 Test: atest FrameworksServicesTests:VibratorServiceTest Change-Id: Ib0122a67d08e380501b71a8652bd4202aae7c002 --- .../com/android/server/VibratorService.java | 154 ++++++- services/tests/servicestests/Android.bp | 1 + .../tests/servicestests/AndroidManifest.xml | 3 + .../android/server/VibratorServiceTest.java | 408 ++++++++++++++++++ 4 files changed, 544 insertions(+), 22 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/VibratorServiceTest.java diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 4ff9eb11c142e..72f29b4318802 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -43,6 +43,7 @@ import android.os.IBinder; import android.os.IExternalVibratorService; import android.os.IVibratorService; import android.os.IVibratorStateListener; +import android.os.Looper; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; @@ -70,6 +71,7 @@ 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; @@ -134,7 +136,7 @@ public class VibratorService extends IVibratorService.Stub private final SparseArray mFallbackEffects; private final SparseArray mProcStatesCache = new SparseArray<>(); private final WorkSource mTmpWorkSource = new WorkSource(); - private final Handler mH = new Handler(); + private final Handler mH; private final Object mLock = new Object(); private final Context mContext; @@ -147,6 +149,7 @@ public class VibratorService extends IVibratorService.Stub private Vibrator mVibrator; private SettingsObserver mSettingObserver; + private final NativeWrapper mNativeWrapper; private volatile VibrateThread mThread; // mInputDeviceVibrators lock should be acquired after mLock, if both are @@ -208,7 +211,12 @@ public class VibratorService extends IVibratorService.Stub } }; - private class Vibration implements IBinder.DeathRecipient { + /** + * Holder for a vibration to be played. This class can be shared with native methods for + * hardware callback support. + */ + @VisibleForTesting + public final class Vibration implements IBinder.DeathRecipient { public final IBinder token; // Start time in CLOCK_BOOTTIME base. public final long startTime; @@ -248,9 +256,9 @@ public class VibratorService extends IVibratorService.Stub } } - // Called by native - @SuppressWarnings("unused") - private void onComplete() { + /** Callback for when vibration is complete, to be called by native. */ + @VisibleForTesting + public void onComplete() { synchronized (mLock) { if (this == mCurrentVibration) { doCancelVibrateLocked(); @@ -354,15 +362,23 @@ public class VibratorService extends IVibratorService.Stub } VibratorService(Context context) { - vibratorInit(); + this(context, new Injector()); + } + + @VisibleForTesting + VibratorService(Context context, Injector injector) { + mNativeWrapper = injector.getNativeWrapper(); + mH = injector.createHandler(Looper.myLooper()); + + mNativeWrapper.vibratorInit(); // Reset the hardware to a default state, in case this is a runtime // restart instead of a fresh boot. - vibratorOff(); + mNativeWrapper.vibratorOff(); - mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl(); - mSupportsExternalControl = vibratorSupportsExternalControl(); - mSupportedEffects = asList(vibratorGetSupportedEffects()); - mCapabilities = vibratorGetCapabilities(); + mSupportsAmplitudeControl = mNativeWrapper.vibratorSupportsAmplitudeControl(); + mSupportsExternalControl = mNativeWrapper.vibratorSupportsExternalControl(); + mSupportedEffects = asList(mNativeWrapper.vibratorGetSupportedEffects()); + mCapabilities = mNativeWrapper.vibratorGetCapabilities(); mContext = context; PowerManager pm = context.getSystemService(PowerManager.class); @@ -419,7 +435,7 @@ public class VibratorService extends IVibratorService.Stub mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_FACTOR_HIGH)); mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_FACTOR_VERY_HIGH)); - ServiceManager.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); + injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); } private VibrationEffect createEffectFromResource(int resId) { @@ -642,7 +658,7 @@ public class VibratorService extends IVibratorService.Stub if (effect == null) { synchronized (mLock) { mAlwaysOnEffects.delete(alwaysOnId); - vibratorAlwaysOnDisable(alwaysOnId); + mNativeWrapper.vibratorAlwaysOnDisable(alwaysOnId); } } else { if (!verifyVibrationEffect(effect)) { @@ -1198,11 +1214,11 @@ public class VibratorService extends IVibratorService.Stub private void updateAlwaysOnLocked(int id, Vibration vib) { final int intensity = getCurrentIntensityLocked(vib); if (!shouldVibrate(vib, intensity)) { - vibratorAlwaysOnDisable(id); + mNativeWrapper.vibratorAlwaysOnDisable(id); } else { final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect; final int strength = intensityToEffectStrength(intensity); - vibratorAlwaysOnEnable(id, prebaked.getId(), strength); + mNativeWrapper.vibratorAlwaysOnEnable(id, prebaked.getId(), strength); } } @@ -1238,7 +1254,7 @@ public class VibratorService extends IVibratorService.Stub //synchronized (mInputDeviceVibrators) { // return !mInputDeviceVibrators.isEmpty() || vibratorExists(); //} - return vibratorExists(); + return mNativeWrapper.vibratorExists(); } private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs) { @@ -1262,7 +1278,7 @@ public class VibratorService extends IVibratorService.Stub // 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. - vibratorOn(millis); + mNativeWrapper.vibratorOn(millis); doVibratorSetAmplitude(amplitude); } } @@ -1273,7 +1289,7 @@ public class VibratorService extends IVibratorService.Stub private void doVibratorSetAmplitude(int amplitude) { if (mSupportsAmplitudeControl) { - vibratorSetAmplitude(amplitude); + mNativeWrapper.vibratorSetAmplitude(amplitude); } } @@ -1291,7 +1307,7 @@ public class VibratorService extends IVibratorService.Stub mInputDeviceVibrators.get(i).cancel(); } } else { - vibratorOff(); + mNativeWrapper.vibratorOff(); } } } finally { @@ -1310,7 +1326,7 @@ public class VibratorService extends IVibratorService.Stub } // Input devices don't support prebaked effect, so skip trying it with them. if (!usingInputDeviceVibrators) { - long duration = vibratorPerformEffect(prebaked.getId(), + long duration = mNativeWrapper.vibratorPerformEffect(prebaked.getId(), prebaked.getEffectStrength(), vib, hasCapability(IVibrator.CAP_PERFORM_CALLBACK)); long timeout = duration; @@ -1363,7 +1379,7 @@ public class VibratorService extends IVibratorService.Stub PrimitiveEffect[] primitiveEffects = composed.getPrimitiveEffects().toArray(new PrimitiveEffect[0]); - vibratorPerformComposedEffect(primitiveEffects, vib); + mNativeWrapper.vibratorPerformComposedEffect(primitiveEffects, vib); // Composed effects don't actually give us an estimated duration, so we just guess here. noteVibratorOnLocked(vib.uid, 10 * primitiveEffects.length); @@ -1454,7 +1470,7 @@ public class VibratorService extends IVibratorService.Stub } } mVibratorUnderExternalControl = externalControl; - vibratorSetExternalControl(externalControl); + mNativeWrapper.vibratorSetExternalControl(externalControl); } private void dumpInternal(PrintWriter pw) { @@ -1688,6 +1704,100 @@ public class VibratorService extends IVibratorService.Stub } } + /** Wrapper around the static-native methods of {@link VibratorService} for tests. */ + @VisibleForTesting + public static class NativeWrapper { + + /** Checks if vibrator exists on device. */ + public boolean vibratorExists() { + return VibratorService.vibratorExists(); + } + + /** Initializes connection to vibrator HAL service. */ + public void vibratorInit() { + VibratorService.vibratorInit(); + } + + /** Turns vibrator on for given time. */ + public void vibratorOn(long milliseconds) { + VibratorService.vibratorOn(milliseconds); + } + + /** Turns vibrator off. */ + public void vibratorOff() { + VibratorService.vibratorOff(); + } + + /** Returns true if vibrator supports {@link #vibratorSetAmplitude(int)}. */ + public boolean vibratorSupportsAmplitudeControl() { + return VibratorService.vibratorSupportsAmplitudeControl(); + } + + /** Sets the amplitude for the vibrator to run. */ + public void vibratorSetAmplitude(int amplitude) { + VibratorService.vibratorSetAmplitude(amplitude); + } + + /** Returns all predefined effects supported by the device vibrator. */ + public int[] vibratorGetSupportedEffects() { + return VibratorService.vibratorGetSupportedEffects(); + } + + /** Turns vibrator on to perform one of the supported effects. */ + public long vibratorPerformEffect(long effect, long strength, Vibration vibration, + boolean withCallback) { + return VibratorService.vibratorPerformEffect(effect, strength, vibration, withCallback); + } + + /** Turns vibrator on to perform one of the supported composed effects. */ + public void vibratorPerformComposedEffect( + VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration) { + VibratorService.vibratorPerformComposedEffect(effect, vibration); + } + + /** Returns true if vibrator supports {@link #vibratorSetExternalControl(boolean)}. */ + public boolean vibratorSupportsExternalControl() { + return VibratorService.vibratorSupportsExternalControl(); + } + + /** Enabled the device vibrator to be controlled by another service. */ + public void vibratorSetExternalControl(boolean enabled) { + VibratorService.vibratorSetExternalControl(enabled); + } + + /** Returns all capabilities of the device vibrator. */ + public long vibratorGetCapabilities() { + return VibratorService.vibratorGetCapabilities(); + } + + /** Enable always-on vibration with given id and effect. */ + public void vibratorAlwaysOnEnable(long id, long effect, long strength) { + VibratorService.vibratorAlwaysOnEnable(id, effect, strength); + } + + /** Disable always-on vibration for given id. */ + public void vibratorAlwaysOnDisable(long id) { + VibratorService.vibratorAlwaysOnDisable(id); + } + } + + /** Point of injection for test dependencies */ + @VisibleForTesting + static class Injector { + + NativeWrapper getNativeWrapper() { + return new NativeWrapper(); + } + + Handler createHandler(Looper looper) { + return new Handler(looper); + } + + void addService(String name, IBinder service) { + ServiceManager.addService(name, service); + } + } + BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index ac2ec58fae2af..7fc6bbd700005 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -59,6 +59,7 @@ android_test { libs: [ "android.hardware.power-java", "android.hardware.tv.cec-V1.0-java", + "android.hardware.vibrator-java", "android.hidl.manager-V1.0-java", "android.test.mock", "android.test.base", diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 6915220d3fb70..90e1cfcd305a9 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -81,6 +81,9 @@ + + + primitivesCaptor = + ArgumentCaptor.forClass(VibrationEffect.Composition.PrimitiveEffect[].class); + + verify(mNativeWrapperMock).vibratorOff(); + verify(mNativeWrapperMock).vibratorPerformComposedEffect( + primitivesCaptor.capture(), any(VibratorService.Vibration.class)); + + // Check all primitive effect fields are passed down to the HAL. + assertEquals(1, primitivesCaptor.getValue().length); + VibrationEffect.Composition.PrimitiveEffect primitive = primitivesCaptor.getValue()[0]; + assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.id); + assertEquals(0.5f, primitive.scale, /* delta= */ 1e-2); + assertEquals(10, primitive.delay); + } + + @Test + public void vibrate_withCallback_finishesVibrationWhenCallbackTriggered() { + mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + VibratorService service = createService(); + Mockito.clearInvocations(mNativeWrapperMock); + + doAnswer(invocation -> { + ((VibratorService.Vibration) invocation.getArgument(1)).onComplete(); + return null; + }).when(mNativeWrapperMock).vibratorPerformComposedEffect( + any(), any(VibratorService.Vibration.class)); + + // Use vibration with delay so there is time for the callback to be triggered. + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10) + .compose(); + vibrate(service, effect); + + // Vibration canceled once before perform and once by native callback. + verify(mNativeWrapperMock, times(2)).vibratorOff(); + verify(mNativeWrapperMock).vibratorPerformComposedEffect( + any(VibrationEffect.Composition.PrimitiveEffect[].class), + any(VibratorService.Vibration.class)); + } + + @Test + public void vibrate_whenBinderDies_cancelsVibration() { + mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + VibratorService service = createService(); + Mockito.clearInvocations(mNativeWrapperMock); + + doAnswer(invocation -> { + ((VibratorService.Vibration) invocation.getArgument(1)).binderDied(); + return null; + }).when(mNativeWrapperMock).vibratorPerformComposedEffect( + any(), any(VibratorService.Vibration.class)); + + // Use vibration with delay so there is time for the callback to be triggered. + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10) + .compose(); + vibrate(service, effect); + + // Vibration canceled once before perform and once by native binder death. + verify(mNativeWrapperMock, times(2)).vibratorOff(); + verify(mNativeWrapperMock).vibratorPerformComposedEffect( + any(VibrationEffect.Composition.PrimitiveEffect[].class), + any(VibratorService.Vibration.class)); + } + + @Test + public void cancelVibrate_withDeviceVibrating_callsVibratorOff() { + VibratorService service = createService(); + vibrate(service, VibrationEffect.createOneShot(100, 128)); + assertTrue(service.isVibrating()); + Mockito.clearInvocations(mNativeWrapperMock); + + service.cancelVibrate(service); + assertFalse(service.isVibrating()); + verify(mNativeWrapperMock).vibratorOff(); + } + + @Test + public void cancelVibrate_withDeviceNotVibrating_ignoresCall() { + VibratorService service = createService(); + Mockito.clearInvocations(mNativeWrapperMock); + + service.cancelVibrate(service); + assertFalse(service.isVibrating()); + verify(mNativeWrapperMock, never()).vibratorOff(); + } + + @Test + public void registerVibratorStateListener_callbacksAreTriggered() throws Exception { + VibratorService service = createService(); + + service.registerVibratorStateListener(mVibratorStateListenerMock); + verify(mVibratorStateListenerMock).onVibrating(false); + + vibrate(service, VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE)); + verify(mVibratorStateListenerMock).onVibrating(true); + + // Run the scheduled callback to finish one-shot vibration. + mTestLooper.moveTimeForward(10); + mTestLooper.dispatchAll(); + verify(mVibratorStateListenerMock, times(2)).onVibrating(false); + } + + @Test + public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception { + VibratorService service = createService(); + + service.registerVibratorStateListener(mVibratorStateListenerMock); + verify(mVibratorStateListenerMock).onVibrating(false); + + vibrate(service, VibrationEffect.createOneShot(5, VibrationEffect.DEFAULT_AMPLITUDE)); + verify(mVibratorStateListenerMock).onVibrating(true); + + service.unregisterVibratorStateListener(mVibratorStateListenerMock); + Mockito.clearInvocations(mVibratorStateListenerMock); + + vibrate(service, VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE)); + verifyNoMoreInteractions(mVibratorStateListenerMock); + } + + private void vibrate(VibratorService service, VibrationEffect effect) { + service.vibrate(UID, PACKAGE_NAME, effect, ALARM_ATTRS, "some reason", service); + } + + private void mockVibratorCapabilities(int capabilities) { + when(mNativeWrapperMock.vibratorGetCapabilities()).thenReturn((long) capabilities); + } + + private static void addLocalServiceMock(Class clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } +}