Loading core/java/android/os/SystemVibrator.java +58 −33 Original line number Diff line number Diff line Loading @@ -50,10 +50,10 @@ public class SystemVibrator extends Vibrator { private final Context mContext; @GuardedBy("mBrokenListeners") private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>(); private final ArrayList<MultiVibratorStateListener> mBrokenListeners = new ArrayList<>(); @GuardedBy("mRegisteredListeners") private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener> private final ArrayMap<OnVibratorStateChangedListener, MultiVibratorStateListener> mRegisteredListeners = new ArrayMap<>(); private final Object mLock = new Object(); Loading Loading @@ -147,7 +147,7 @@ public class SystemVibrator extends Vibrator { Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager."); return; } AllVibratorsStateListener delegate = null; MultiVibratorStateListener delegate = null; try { synchronized (mRegisteredListeners) { // If listener is already registered, reject and return. Loading @@ -155,7 +155,7 @@ public class SystemVibrator extends Vibrator { Log.w(TAG, "Listener already registered."); return; } delegate = new AllVibratorsStateListener(executor, listener); delegate = new MultiVibratorStateListener(executor, listener); delegate.register(mVibratorManager); mRegisteredListeners.put(listener, delegate); delegate = null; Loading @@ -181,7 +181,7 @@ public class SystemVibrator extends Vibrator { } synchronized (mRegisteredListeners) { if (mRegisteredListeners.containsKey(listener)) { AllVibratorsStateListener delegate = mRegisteredListeners.get(listener); MultiVibratorStateListener delegate = mRegisteredListeners.get(listener); delegate.unregister(mVibratorManager); mRegisteredListeners.remove(listener); } Loading Loading @@ -238,7 +238,7 @@ public class SystemVibrator extends Vibrator { * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener} * that were left registered to vibrators after failures to register them to all vibrators. * * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator * <p>This might happen if {@link MultiVibratorStateListener} fails to register to any vibrator * and also fails to unregister any previously registered single listeners to other vibrators. * * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will Loading @@ -259,10 +259,10 @@ public class SystemVibrator extends Vibrator { /** Listener for a single vibrator state change. */ private static class SingleVibratorStateListener implements OnVibratorStateChangedListener { private final AllVibratorsStateListener mAllVibratorsListener; private final MultiVibratorStateListener mAllVibratorsListener; private final int mVibratorIdx; SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) { SingleVibratorStateListener(MultiVibratorStateListener listener, int vibratorIdx) { mAllVibratorsListener = listener; mVibratorIdx = vibratorIdx; } Loading Loading @@ -552,8 +552,16 @@ public class SystemVibrator extends Vibrator { } } /** Listener for all vibrators state change. */ private static class AllVibratorsStateListener { /** * Listener for all vibrators state change. * * <p>This registers a listener to all vibrators to merge the callbacks into a single state * that is set to true if any individual vibrator is also true, and false otherwise. * * @hide */ @VisibleForTesting public static class MultiVibratorStateListener { private final Object mLock = new Object(); private final Executor mExecutor; private final OnVibratorStateChangedListener mDelegate; Loading @@ -567,19 +575,21 @@ public class SystemVibrator extends Vibrator { @GuardedBy("mLock") private int mVibratingMask; AllVibratorsStateListener(@NonNull Executor executor, public MultiVibratorStateListener(@NonNull Executor executor, @NonNull OnVibratorStateChangedListener listener) { mExecutor = executor; mDelegate = listener; } boolean hasRegisteredListeners() { /** Returns true if at least one listener was registered to an individual vibrator. */ public boolean hasRegisteredListeners() { synchronized (mLock) { return mVibratorListeners.size() > 0; } } void register(VibratorManager vibratorManager) { /** Registers a listener to all individual vibrators in {@link VibratorManager}. */ public void register(VibratorManager vibratorManager) { int[] vibratorIds = vibratorManager.getVibratorIds(); synchronized (mLock) { for (int i = 0; i < vibratorIds.length; i++) { Loading @@ -603,7 +613,8 @@ public class SystemVibrator extends Vibrator { } } void unregister(VibratorManager vibratorManager) { /** Unregisters the listeners from all individual vibrators in {@link VibratorManager}. */ public void unregister(VibratorManager vibratorManager) { synchronized (mLock) { for (int i = mVibratorListeners.size(); --i >= 0; ) { int vibratorId = mVibratorListeners.keyAt(i); Loading @@ -614,30 +625,44 @@ public class SystemVibrator extends Vibrator { } } void onVibrating(int vibratorIdx, boolean vibrating) { /** Callback triggered by {@link SingleVibratorStateListener} for each vibrator. */ public void onVibrating(int vibratorIdx, boolean vibrating) { mExecutor.execute(() -> { boolean anyVibrating; boolean shouldNotifyStateChange; boolean isAnyVibrating; synchronized (mLock) { // Bitmask indicating that all vibrators have been initialized. int allInitializedMask = (1 << mVibratorListeners.size()) - 1; int vibratorMask = 1 << vibratorIdx; if ((mInitializedMask & vibratorMask) == 0) { // First state report for this vibrator, set vibrating initial value. // Save current global state before processing this vibrator state change. boolean previousIsAnyVibrating = (mVibratingMask != 0); boolean previousAreAllInitialized = (mInitializedMask == allInitializedMask); // Mark this vibrator as initialized. int vibratorMask = (1 << vibratorIdx); mInitializedMask |= vibratorMask; mVibratingMask |= vibrating ? vibratorMask : 0; } else { // Flip vibrating value, if changed. boolean prevVibrating = (mVibratingMask & vibratorMask) != 0; if (prevVibrating != vibrating) { // Flip the vibrating bit flag for this vibrator, only if the state is changing. boolean previousVibrating = (mVibratingMask & vibratorMask) != 0; if (previousVibrating != vibrating) { mVibratingMask ^= vibratorMask; } // Check new global state after processing this vibrator state change. isAnyVibrating = (mVibratingMask != 0); boolean areAllInitialized = (mInitializedMask == allInitializedMask); // Prevent multiple triggers with the same state. // Trigger once when all vibrators have reported their state, and then only when // the merged vibrating state changes. boolean isStateChanging = (previousIsAnyVibrating != isAnyVibrating); shouldNotifyStateChange = areAllInitialized && (!previousAreAllInitialized || isStateChanging); } if (mInitializedMask != allInitializedMask) { // Wait for all vibrators initial state to be reported before delegating. return; } anyVibrating = mVibratingMask != 0; // Notify delegate listener outside the lock, only if merged state is changing. if (shouldNotifyStateChange) { mDelegate.onVibratorStateChanged(isAnyVibrating); } mDelegate.onVibratorStateChanged(anyVibrating); }); } } Loading core/tests/coretests/src/android/os/VibratorTest.java +111 −0 Original line number Diff line number Diff line Loading @@ -21,12 +21,17 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.ContentResolver; Loading @@ -34,6 +39,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.hardware.vibrator.IVibrator; import android.media.AudioAttributes; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; Loading @@ -46,6 +52,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.junit.MockitoJUnitRunner; /** Loading @@ -65,6 +72,7 @@ public class VibratorTest { private Context mContextSpy; private Vibrator mVibratorSpy; private TestLooper mTestLooper; @Before public void setUp() { Loading @@ -73,6 +81,7 @@ public class VibratorTest { ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); when(mContextSpy.getContentResolver()).thenReturn(contentResolver); mVibratorSpy = spy(new SystemVibrator(mContextSpy)); mTestLooper = new TestLooper(); } @Test Loading Loading @@ -394,6 +403,108 @@ public class VibratorTest { info.getFrequencyProfile()); } @Test public void onVibratorStateChanged_noVibrator_registersNoListenerToVibratorManager() { VibratorManager mockVibratorManager = mock(VibratorManager.class); when(mockVibratorManager.getVibratorIds()).thenReturn(new int[0]); Vibrator.OnVibratorStateChangedListener mockListener = mock(Vibrator.OnVibratorStateChangedListener.class); SystemVibrator.MultiVibratorStateListener multiVibratorListener = new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); multiVibratorListener.register(mockVibratorManager); // Never tries to register a listener to an individual vibrator. assertFalse(multiVibratorListener.hasRegisteredListeners()); verify(mockVibratorManager, never()).getVibrator(anyInt()); } @Test public void onVibratorStateChanged_singleVibrator_forwardsAllCallbacks() { VibratorManager mockVibratorManager = mock(VibratorManager.class); when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1 }); when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance()); Vibrator.OnVibratorStateChangedListener mockListener = mock(Vibrator.OnVibratorStateChangedListener.class); SystemVibrator.MultiVibratorStateListener multiVibratorListener = new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); multiVibratorListener.register(mockVibratorManager); assertTrue(multiVibratorListener.hasRegisteredListeners()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ true); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); mTestLooper.dispatchAll(); InOrder inOrder = inOrder(mockListener); inOrder.verify(mockListener).onVibratorStateChanged(eq(false)); inOrder.verify(mockListener).onVibratorStateChanged(eq(true)); inOrder.verify(mockListener).onVibratorStateChanged(eq(false)); inOrder.verifyNoMoreInteractions(); } @Test public void onVibratorStateChanged_multipleVibrators_triggersOnlyWhenAllVibratorsInitialized() { VibratorManager mockVibratorManager = mock(VibratorManager.class); when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 }); when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance()); Vibrator.OnVibratorStateChangedListener mockListener = mock(Vibrator.OnVibratorStateChangedListener.class); SystemVibrator.MultiVibratorStateListener multiVibratorListener = new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); multiVibratorListener.register(mockVibratorManager); assertTrue(multiVibratorListener.hasRegisteredListeners()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); mTestLooper.dispatchAll(); verify(mockListener, never()).onVibratorStateChanged(anyBoolean()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); mTestLooper.dispatchAll(); verify(mockListener).onVibratorStateChanged(eq(false)); verifyNoMoreInteractions(mockListener); } @Test public void onVibratorStateChanged_multipleVibrators_stateChangeIsDeduped() { VibratorManager mockVibratorManager = mock(VibratorManager.class); when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 }); when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance()); Vibrator.OnVibratorStateChangedListener mockListener = mock(Vibrator.OnVibratorStateChangedListener.class); SystemVibrator.MultiVibratorStateListener multiVibratorListener = new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); multiVibratorListener.register(mockVibratorManager); assertTrue(multiVibratorListener.hasRegisteredListeners()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // none multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); // false multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ true); // true multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ true); // true multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // true multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); // false mTestLooper.dispatchAll(); InOrder inOrder = inOrder(mockListener); inOrder.verify(mockListener).onVibratorStateChanged(eq(false)); inOrder.verify(mockListener).onVibratorStateChanged(eq(true)); inOrder.verify(mockListener).onVibratorStateChanged(eq(false)); inOrder.verifyNoMoreInteractions(); } @Test public void vibrate_withVibrationAttributes_usesGivenAttributes() { VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); Loading Loading
core/java/android/os/SystemVibrator.java +58 −33 Original line number Diff line number Diff line Loading @@ -50,10 +50,10 @@ public class SystemVibrator extends Vibrator { private final Context mContext; @GuardedBy("mBrokenListeners") private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>(); private final ArrayList<MultiVibratorStateListener> mBrokenListeners = new ArrayList<>(); @GuardedBy("mRegisteredListeners") private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener> private final ArrayMap<OnVibratorStateChangedListener, MultiVibratorStateListener> mRegisteredListeners = new ArrayMap<>(); private final Object mLock = new Object(); Loading Loading @@ -147,7 +147,7 @@ public class SystemVibrator extends Vibrator { Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager."); return; } AllVibratorsStateListener delegate = null; MultiVibratorStateListener delegate = null; try { synchronized (mRegisteredListeners) { // If listener is already registered, reject and return. Loading @@ -155,7 +155,7 @@ public class SystemVibrator extends Vibrator { Log.w(TAG, "Listener already registered."); return; } delegate = new AllVibratorsStateListener(executor, listener); delegate = new MultiVibratorStateListener(executor, listener); delegate.register(mVibratorManager); mRegisteredListeners.put(listener, delegate); delegate = null; Loading @@ -181,7 +181,7 @@ public class SystemVibrator extends Vibrator { } synchronized (mRegisteredListeners) { if (mRegisteredListeners.containsKey(listener)) { AllVibratorsStateListener delegate = mRegisteredListeners.get(listener); MultiVibratorStateListener delegate = mRegisteredListeners.get(listener); delegate.unregister(mVibratorManager); mRegisteredListeners.remove(listener); } Loading Loading @@ -238,7 +238,7 @@ public class SystemVibrator extends Vibrator { * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener} * that were left registered to vibrators after failures to register them to all vibrators. * * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator * <p>This might happen if {@link MultiVibratorStateListener} fails to register to any vibrator * and also fails to unregister any previously registered single listeners to other vibrators. * * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will Loading @@ -259,10 +259,10 @@ public class SystemVibrator extends Vibrator { /** Listener for a single vibrator state change. */ private static class SingleVibratorStateListener implements OnVibratorStateChangedListener { private final AllVibratorsStateListener mAllVibratorsListener; private final MultiVibratorStateListener mAllVibratorsListener; private final int mVibratorIdx; SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) { SingleVibratorStateListener(MultiVibratorStateListener listener, int vibratorIdx) { mAllVibratorsListener = listener; mVibratorIdx = vibratorIdx; } Loading Loading @@ -552,8 +552,16 @@ public class SystemVibrator extends Vibrator { } } /** Listener for all vibrators state change. */ private static class AllVibratorsStateListener { /** * Listener for all vibrators state change. * * <p>This registers a listener to all vibrators to merge the callbacks into a single state * that is set to true if any individual vibrator is also true, and false otherwise. * * @hide */ @VisibleForTesting public static class MultiVibratorStateListener { private final Object mLock = new Object(); private final Executor mExecutor; private final OnVibratorStateChangedListener mDelegate; Loading @@ -567,19 +575,21 @@ public class SystemVibrator extends Vibrator { @GuardedBy("mLock") private int mVibratingMask; AllVibratorsStateListener(@NonNull Executor executor, public MultiVibratorStateListener(@NonNull Executor executor, @NonNull OnVibratorStateChangedListener listener) { mExecutor = executor; mDelegate = listener; } boolean hasRegisteredListeners() { /** Returns true if at least one listener was registered to an individual vibrator. */ public boolean hasRegisteredListeners() { synchronized (mLock) { return mVibratorListeners.size() > 0; } } void register(VibratorManager vibratorManager) { /** Registers a listener to all individual vibrators in {@link VibratorManager}. */ public void register(VibratorManager vibratorManager) { int[] vibratorIds = vibratorManager.getVibratorIds(); synchronized (mLock) { for (int i = 0; i < vibratorIds.length; i++) { Loading @@ -603,7 +613,8 @@ public class SystemVibrator extends Vibrator { } } void unregister(VibratorManager vibratorManager) { /** Unregisters the listeners from all individual vibrators in {@link VibratorManager}. */ public void unregister(VibratorManager vibratorManager) { synchronized (mLock) { for (int i = mVibratorListeners.size(); --i >= 0; ) { int vibratorId = mVibratorListeners.keyAt(i); Loading @@ -614,30 +625,44 @@ public class SystemVibrator extends Vibrator { } } void onVibrating(int vibratorIdx, boolean vibrating) { /** Callback triggered by {@link SingleVibratorStateListener} for each vibrator. */ public void onVibrating(int vibratorIdx, boolean vibrating) { mExecutor.execute(() -> { boolean anyVibrating; boolean shouldNotifyStateChange; boolean isAnyVibrating; synchronized (mLock) { // Bitmask indicating that all vibrators have been initialized. int allInitializedMask = (1 << mVibratorListeners.size()) - 1; int vibratorMask = 1 << vibratorIdx; if ((mInitializedMask & vibratorMask) == 0) { // First state report for this vibrator, set vibrating initial value. // Save current global state before processing this vibrator state change. boolean previousIsAnyVibrating = (mVibratingMask != 0); boolean previousAreAllInitialized = (mInitializedMask == allInitializedMask); // Mark this vibrator as initialized. int vibratorMask = (1 << vibratorIdx); mInitializedMask |= vibratorMask; mVibratingMask |= vibrating ? vibratorMask : 0; } else { // Flip vibrating value, if changed. boolean prevVibrating = (mVibratingMask & vibratorMask) != 0; if (prevVibrating != vibrating) { // Flip the vibrating bit flag for this vibrator, only if the state is changing. boolean previousVibrating = (mVibratingMask & vibratorMask) != 0; if (previousVibrating != vibrating) { mVibratingMask ^= vibratorMask; } // Check new global state after processing this vibrator state change. isAnyVibrating = (mVibratingMask != 0); boolean areAllInitialized = (mInitializedMask == allInitializedMask); // Prevent multiple triggers with the same state. // Trigger once when all vibrators have reported their state, and then only when // the merged vibrating state changes. boolean isStateChanging = (previousIsAnyVibrating != isAnyVibrating); shouldNotifyStateChange = areAllInitialized && (!previousAreAllInitialized || isStateChanging); } if (mInitializedMask != allInitializedMask) { // Wait for all vibrators initial state to be reported before delegating. return; } anyVibrating = mVibratingMask != 0; // Notify delegate listener outside the lock, only if merged state is changing. if (shouldNotifyStateChange) { mDelegate.onVibratorStateChanged(isAnyVibrating); } mDelegate.onVibratorStateChanged(anyVibrating); }); } } Loading
core/tests/coretests/src/android/os/VibratorTest.java +111 −0 Original line number Diff line number Diff line Loading @@ -21,12 +21,17 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.ContentResolver; Loading @@ -34,6 +39,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.hardware.vibrator.IVibrator; import android.media.AudioAttributes; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; Loading @@ -46,6 +52,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.junit.MockitoJUnitRunner; /** Loading @@ -65,6 +72,7 @@ public class VibratorTest { private Context mContextSpy; private Vibrator mVibratorSpy; private TestLooper mTestLooper; @Before public void setUp() { Loading @@ -73,6 +81,7 @@ public class VibratorTest { ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); when(mContextSpy.getContentResolver()).thenReturn(contentResolver); mVibratorSpy = spy(new SystemVibrator(mContextSpy)); mTestLooper = new TestLooper(); } @Test Loading Loading @@ -394,6 +403,108 @@ public class VibratorTest { info.getFrequencyProfile()); } @Test public void onVibratorStateChanged_noVibrator_registersNoListenerToVibratorManager() { VibratorManager mockVibratorManager = mock(VibratorManager.class); when(mockVibratorManager.getVibratorIds()).thenReturn(new int[0]); Vibrator.OnVibratorStateChangedListener mockListener = mock(Vibrator.OnVibratorStateChangedListener.class); SystemVibrator.MultiVibratorStateListener multiVibratorListener = new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); multiVibratorListener.register(mockVibratorManager); // Never tries to register a listener to an individual vibrator. assertFalse(multiVibratorListener.hasRegisteredListeners()); verify(mockVibratorManager, never()).getVibrator(anyInt()); } @Test public void onVibratorStateChanged_singleVibrator_forwardsAllCallbacks() { VibratorManager mockVibratorManager = mock(VibratorManager.class); when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1 }); when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance()); Vibrator.OnVibratorStateChangedListener mockListener = mock(Vibrator.OnVibratorStateChangedListener.class); SystemVibrator.MultiVibratorStateListener multiVibratorListener = new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); multiVibratorListener.register(mockVibratorManager); assertTrue(multiVibratorListener.hasRegisteredListeners()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ true); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); mTestLooper.dispatchAll(); InOrder inOrder = inOrder(mockListener); inOrder.verify(mockListener).onVibratorStateChanged(eq(false)); inOrder.verify(mockListener).onVibratorStateChanged(eq(true)); inOrder.verify(mockListener).onVibratorStateChanged(eq(false)); inOrder.verifyNoMoreInteractions(); } @Test public void onVibratorStateChanged_multipleVibrators_triggersOnlyWhenAllVibratorsInitialized() { VibratorManager mockVibratorManager = mock(VibratorManager.class); when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 }); when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance()); Vibrator.OnVibratorStateChangedListener mockListener = mock(Vibrator.OnVibratorStateChangedListener.class); SystemVibrator.MultiVibratorStateListener multiVibratorListener = new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); multiVibratorListener.register(mockVibratorManager); assertTrue(multiVibratorListener.hasRegisteredListeners()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); mTestLooper.dispatchAll(); verify(mockListener, never()).onVibratorStateChanged(anyBoolean()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); mTestLooper.dispatchAll(); verify(mockListener).onVibratorStateChanged(eq(false)); verifyNoMoreInteractions(mockListener); } @Test public void onVibratorStateChanged_multipleVibrators_stateChangeIsDeduped() { VibratorManager mockVibratorManager = mock(VibratorManager.class); when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 }); when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance()); Vibrator.OnVibratorStateChangedListener mockListener = mock(Vibrator.OnVibratorStateChangedListener.class); SystemVibrator.MultiVibratorStateListener multiVibratorListener = new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); multiVibratorListener.register(mockVibratorManager); assertTrue(multiVibratorListener.hasRegisteredListeners()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // none multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); // false multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ true); // true multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ true); // true multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // true multiVibratorListener.onVibrating(/* vibratorIdx= */ 1, /* vibrating= */ false); // false mTestLooper.dispatchAll(); InOrder inOrder = inOrder(mockListener); inOrder.verify(mockListener).onVibratorStateChanged(eq(false)); inOrder.verify(mockListener).onVibratorStateChanged(eq(true)); inOrder.verify(mockListener).onVibratorStateChanged(eq(false)); inOrder.verifyNoMoreInteractions(); } @Test public void vibrate_withVibrationAttributes_usesGivenAttributes() { VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); Loading