Merge "Dedupe vibrator state in SystemVibrator callbacks" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4f0fcbf139
@@ -50,10 +50,10 @@ public class SystemVibrator extends Vibrator {
|
|||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
|
||||||
@GuardedBy("mBrokenListeners")
|
@GuardedBy("mBrokenListeners")
|
||||||
private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
|
private final ArrayList<MultiVibratorStateListener> mBrokenListeners = new ArrayList<>();
|
||||||
|
|
||||||
@GuardedBy("mRegisteredListeners")
|
@GuardedBy("mRegisteredListeners")
|
||||||
private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
|
private final ArrayMap<OnVibratorStateChangedListener, MultiVibratorStateListener>
|
||||||
mRegisteredListeners = new ArrayMap<>();
|
mRegisteredListeners = new ArrayMap<>();
|
||||||
|
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
@@ -147,7 +147,7 @@ public class SystemVibrator extends Vibrator {
|
|||||||
Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
|
Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AllVibratorsStateListener delegate = null;
|
MultiVibratorStateListener delegate = null;
|
||||||
try {
|
try {
|
||||||
synchronized (mRegisteredListeners) {
|
synchronized (mRegisteredListeners) {
|
||||||
// If listener is already registered, reject and return.
|
// If listener is already registered, reject and return.
|
||||||
@@ -155,7 +155,7 @@ public class SystemVibrator extends Vibrator {
|
|||||||
Log.w(TAG, "Listener already registered.");
|
Log.w(TAG, "Listener already registered.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
delegate = new AllVibratorsStateListener(executor, listener);
|
delegate = new MultiVibratorStateListener(executor, listener);
|
||||||
delegate.register(mVibratorManager);
|
delegate.register(mVibratorManager);
|
||||||
mRegisteredListeners.put(listener, delegate);
|
mRegisteredListeners.put(listener, delegate);
|
||||||
delegate = null;
|
delegate = null;
|
||||||
@@ -181,7 +181,7 @@ public class SystemVibrator extends Vibrator {
|
|||||||
}
|
}
|
||||||
synchronized (mRegisteredListeners) {
|
synchronized (mRegisteredListeners) {
|
||||||
if (mRegisteredListeners.containsKey(listener)) {
|
if (mRegisteredListeners.containsKey(listener)) {
|
||||||
AllVibratorsStateListener delegate = mRegisteredListeners.get(listener);
|
MultiVibratorStateListener delegate = mRegisteredListeners.get(listener);
|
||||||
delegate.unregister(mVibratorManager);
|
delegate.unregister(mVibratorManager);
|
||||||
mRegisteredListeners.remove(listener);
|
mRegisteredListeners.remove(listener);
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ public class SystemVibrator extends Vibrator {
|
|||||||
* Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
|
* Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
|
||||||
* that were left registered to vibrators after failures to register them to all vibrators.
|
* 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.
|
* 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
|
* <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
|
||||||
@@ -259,10 +259,10 @@ public class SystemVibrator extends Vibrator {
|
|||||||
|
|
||||||
/** Listener for a single vibrator state change. */
|
/** Listener for a single vibrator state change. */
|
||||||
private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
|
private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
|
||||||
private final AllVibratorsStateListener mAllVibratorsListener;
|
private final MultiVibratorStateListener mAllVibratorsListener;
|
||||||
private final int mVibratorIdx;
|
private final int mVibratorIdx;
|
||||||
|
|
||||||
SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) {
|
SingleVibratorStateListener(MultiVibratorStateListener listener, int vibratorIdx) {
|
||||||
mAllVibratorsListener = listener;
|
mAllVibratorsListener = listener;
|
||||||
mVibratorIdx = vibratorIdx;
|
mVibratorIdx = vibratorIdx;
|
||||||
}
|
}
|
||||||
@@ -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 Object mLock = new Object();
|
||||||
private final Executor mExecutor;
|
private final Executor mExecutor;
|
||||||
private final OnVibratorStateChangedListener mDelegate;
|
private final OnVibratorStateChangedListener mDelegate;
|
||||||
@@ -567,19 +575,21 @@ public class SystemVibrator extends Vibrator {
|
|||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private int mVibratingMask;
|
private int mVibratingMask;
|
||||||
|
|
||||||
AllVibratorsStateListener(@NonNull Executor executor,
|
public MultiVibratorStateListener(@NonNull Executor executor,
|
||||||
@NonNull OnVibratorStateChangedListener listener) {
|
@NonNull OnVibratorStateChangedListener listener) {
|
||||||
mExecutor = executor;
|
mExecutor = executor;
|
||||||
mDelegate = listener;
|
mDelegate = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasRegisteredListeners() {
|
/** Returns true if at least one listener was registered to an individual vibrator. */
|
||||||
|
public boolean hasRegisteredListeners() {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
return mVibratorListeners.size() > 0;
|
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();
|
int[] vibratorIds = vibratorManager.getVibratorIds();
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
for (int i = 0; i < vibratorIds.length; i++) {
|
for (int i = 0; i < vibratorIds.length; i++) {
|
||||||
@@ -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) {
|
synchronized (mLock) {
|
||||||
for (int i = mVibratorListeners.size(); --i >= 0; ) {
|
for (int i = mVibratorListeners.size(); --i >= 0; ) {
|
||||||
int vibratorId = mVibratorListeners.keyAt(i);
|
int vibratorId = mVibratorListeners.keyAt(i);
|
||||||
@@ -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(() -> {
|
mExecutor.execute(() -> {
|
||||||
boolean anyVibrating;
|
boolean shouldNotifyStateChange;
|
||||||
|
boolean isAnyVibrating;
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
|
// Bitmask indicating that all vibrators have been initialized.
|
||||||
int allInitializedMask = (1 << mVibratorListeners.size()) - 1;
|
int allInitializedMask = (1 << mVibratorListeners.size()) - 1;
|
||||||
int vibratorMask = 1 << vibratorIdx;
|
|
||||||
if ((mInitializedMask & vibratorMask) == 0) {
|
// Save current global state before processing this vibrator state change.
|
||||||
// First state report for this vibrator, set vibrating initial value.
|
boolean previousIsAnyVibrating = (mVibratingMask != 0);
|
||||||
mInitializedMask |= vibratorMask;
|
boolean previousAreAllInitialized = (mInitializedMask == allInitializedMask);
|
||||||
mVibratingMask |= vibrating ? vibratorMask : 0;
|
|
||||||
} else {
|
// Mark this vibrator as initialized.
|
||||||
// Flip vibrating value, if changed.
|
int vibratorMask = (1 << vibratorIdx);
|
||||||
boolean prevVibrating = (mVibratingMask & vibratorMask) != 0;
|
mInitializedMask |= vibratorMask;
|
||||||
if (prevVibrating != vibrating) {
|
|
||||||
mVibratingMask ^= vibratorMask;
|
// Flip the vibrating bit flag for this vibrator, only if the state is changing.
|
||||||
}
|
boolean previousVibrating = (mVibratingMask & vibratorMask) != 0;
|
||||||
|
if (previousVibrating != vibrating) {
|
||||||
|
mVibratingMask ^= vibratorMask;
|
||||||
}
|
}
|
||||||
if (mInitializedMask != allInitializedMask) {
|
|
||||||
// Wait for all vibrators initial state to be reported before delegating.
|
// Check new global state after processing this vibrator state change.
|
||||||
return;
|
isAnyVibrating = (mVibratingMask != 0);
|
||||||
}
|
boolean areAllInitialized = (mInitializedMask == allInitializedMask);
|
||||||
anyVibrating = mVibratingMask != 0;
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
// Notify delegate listener outside the lock, only if merged state is changing.
|
||||||
|
if (shouldNotifyStateChange) {
|
||||||
|
mDelegate.onVibratorStateChanged(isAnyVibrating);
|
||||||
}
|
}
|
||||||
mDelegate.onVibratorStateChanged(anyVibrating);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,17 @@ import static junit.framework.Assert.assertTrue;
|
|||||||
import static junit.framework.TestCase.assertEquals;
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.isNull;
|
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.spy;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
@@ -34,6 +39,7 @@ import android.content.Context;
|
|||||||
import android.content.ContextWrapper;
|
import android.content.ContextWrapper;
|
||||||
import android.hardware.vibrator.IVibrator;
|
import android.hardware.vibrator.IVibrator;
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
|
import android.os.test.TestLooper;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
@@ -46,6 +52,7 @@ import org.junit.Rule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InOrder;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,6 +72,7 @@ public class VibratorTest {
|
|||||||
|
|
||||||
private Context mContextSpy;
|
private Context mContextSpy;
|
||||||
private Vibrator mVibratorSpy;
|
private Vibrator mVibratorSpy;
|
||||||
|
private TestLooper mTestLooper;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -73,6 +81,7 @@ public class VibratorTest {
|
|||||||
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
|
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
|
||||||
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
|
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
|
||||||
mVibratorSpy = spy(new SystemVibrator(mContextSpy));
|
mVibratorSpy = spy(new SystemVibrator(mContextSpy));
|
||||||
|
mTestLooper = new TestLooper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -394,6 +403,108 @@ public class VibratorTest {
|
|||||||
info.getFrequencyProfile());
|
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
|
@Test
|
||||||
public void vibrate_withVibrationAttributes_usesGivenAttributes() {
|
public void vibrate_withVibrationAttributes_usesGivenAttributes() {
|
||||||
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
||||||
|
|||||||
Reference in New Issue
Block a user