Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4f0fcbf1 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Dedupe vibrator state in SystemVibrator callbacks" into tm-qpr-dev

parents 800f8fa2 f1786a74
Loading
Loading
Loading
Loading
+58 −33
Original line number Diff line number Diff line
@@ -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();
@@ -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.
@@ -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;
@@ -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);
            }
@@ -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
@@ -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;
        }
@@ -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;
@@ -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++) {
@@ -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);
@@ -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);
            });
        }
    }
+111 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;

/**
@@ -65,6 +72,7 @@ public class VibratorTest {

    private Context mContextSpy;
    private Vibrator mVibratorSpy;
    private TestLooper mTestLooper;

    @Before
    public void setUp() {
@@ -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
@@ -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);