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

Commit deaadea2 authored by William Escande's avatar William Escande
Browse files

HeadsetTest: Use injected test looper and Truth

By using injected test looper, we can now remove the need to use sleep
in test that induced flakiness
By moving to truth we can now have better error message when an
exception is thrown

Fix flakiness >5K iteration on each passed, before it failed in the
first 100

Remove check "isOnStateMachineThread" as this is silently discarding
message and not compatible with a TestLooper created in Setup

Test: atest HeadsetServiceAndStateMachineTest --rerun-until-failure
Test: atest HeadsetServiceTest --rerun-until-failure
Test: atest HeadsetStateMachineTest --rerun-until-failure
Flag: Exempt, only modify logic in test
Bug: 338303046
Change-Id: I0507fe4bb9e4a3abe4bb1be1defa7e2435566635
parent ff452897
Loading
Loading
Loading
Loading
+89 −84
Original line number Diff line number Diff line
@@ -134,12 +134,13 @@ public class HeadsetService extends ProfileService {
    private final DatabaseManager mDatabaseManager;
    private final HeadsetNativeInterface mNativeInterface;
    private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>();
    private final Handler mHandler;
    private final Looper mStateMachinesLooper;
    private final Handler mStateMachinesThreadHandler;
    private final HandlerThread mStateMachinesThread;

    private int mMaxHeadsetConnections = 1;
    private BluetoothDevice mActiveDevice;
    private HandlerThread mStateMachinesThread;
    private Handler mStateMachinesThreadHandler;
    private Handler mHandler;
    // This is also used as a lock for shared data in HeadsetService
    private HeadsetSystemInterface mSystemInterface;
    private boolean mAudioRouteAllowed = true;
@@ -164,15 +165,32 @@ public class HeadsetService extends ProfileService {
    @VisibleForTesting ServiceFactory mFactory = new ServiceFactory();

    public HeadsetService(AdapterService adapterService) {
        this(adapterService, HeadsetNativeInterface.getInstance());
        this(adapterService, HeadsetNativeInterface.getInstance(), null);
    }

    @VisibleForTesting
    HeadsetService(AdapterService adapterService, HeadsetNativeInterface nativeInterface) {
        this(adapterService, nativeInterface, null);
    }

    @VisibleForTesting
    HeadsetService(
            AdapterService adapterService, HeadsetNativeInterface nativeInterface, Looper looper) {
        super(requireNonNull(adapterService));
        mAdapterService = adapterService;
        mDatabaseManager = requireNonNull(mAdapterService.getDatabase());
        mNativeInterface = requireNonNull(nativeInterface);
        if (looper != null) {
            mHandler = new Handler(looper);
            mStateMachinesThread = null;
            mStateMachinesLooper = looper;
        } else {
            mHandler = new Handler(Looper.getMainLooper());
            mStateMachinesThread = new HandlerThread("HeadsetService.StateMachines");
            mStateMachinesThread.start();
            mStateMachinesLooper = mStateMachinesThread.getLooper();
        }
        mStateMachinesThreadHandler = new Handler(mStateMachinesLooper);
    }

    public static boolean isEnabled() {
@@ -193,10 +211,6 @@ public class HeadsetService extends ProfileService {

        setComponentAvailable(HFP_AG_IN_CALL_SERVICE, true);

        // Step 2: Start handler thread for state machines
        mHandler = new Handler(Looper.getMainLooper());
        mStateMachinesThread = new HandlerThread("HeadsetService.StateMachines");
        mStateMachinesThread.start();
        // Step 3: Initialize system interface
        mSystemInterface = HeadsetObjectsFactory.getInstance().makeSystemInterface(this);
        // Step 4: Initialize native interface
@@ -259,13 +273,11 @@ public class HeadsetService extends ProfileService {
            mVoiceRecognitionStarted = false;
            mVirtualCallStarted = false;
            if (mDialingOutTimeoutEvent != null) {
                getStateMachinesThreadHandler()
                        .removeCallbacks(mDialingOutTimeoutEvent);
                mStateMachinesThreadHandler.removeCallbacks(mDialingOutTimeoutEvent);
                mDialingOutTimeoutEvent = null;
            }
            if (mVoiceRecognitionTimeoutEvent != null) {
                getStateMachinesThreadHandler()
                        .removeCallbacks(mVoiceRecognitionTimeoutEvent);
                mStateMachinesThreadHandler.removeCallbacks(mVoiceRecognitionTimeoutEvent);
                mVoiceRecognitionTimeoutEvent = null;
                if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                    mSystemInterface.getVoiceRecognitionWakeLock().release();
@@ -283,21 +295,17 @@ public class HeadsetService extends ProfileService {
        // Step 3: Destroy system interface
        mSystemInterface.stop();
        // Step 2: Stop handler thread
        if (mStateMachinesThread != null) {
            try {
                mStateMachinesThread.quitSafely();
                mStateMachinesThread.join(SM_THREAD_JOIN_TIMEOUT_MS);
            mStateMachinesThread = null;
            } catch (InterruptedException e) {
                // Do not rethrow as we are shutting down anyway
            }

        mStateMachinesThreadHandler = null;
        }

        // Unregister Handler and stop all queued messages.
        if (mHandler != null) {
        mHandler.removeCallbacksAndMessages(null);
            mHandler = null;
        }

        // Step 1: Clear
        setComponentAvailable(HFP_AG_IN_CALL_SERVICE, false);
@@ -384,10 +392,15 @@ public class HeadsetService extends ProfileService {
                    case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: {
                        // Create new state machine if none is found
                        if (stateMachine == null) {
                            stateMachine = HeadsetObjectsFactory.getInstance()
                                    .makeStateMachine(stackEvent.device,
                                            mStateMachinesThread.getLooper(), this, mAdapterService,
                                            mNativeInterface, mSystemInterface);
                                stateMachine =
                                        HeadsetObjectsFactory.getInstance()
                                                .makeStateMachine(
                                                        stackEvent.device,
                                                        mStateMachinesLooper,
                                                        this,
                                                        mAdapterService,
                                                        mNativeInterface,
                                                        mSystemInterface);
                            mStateMachines.put(stackEvent.device, stateMachine);
                        }
                        break;
@@ -850,9 +863,15 @@ public class HeadsetService extends ProfileService {
            Log.i(TAG, "connect: device=" + device + ", " + Utils.getUidPidString());
            HeadsetStateMachine stateMachine = mStateMachines.get(device);
            if (stateMachine == null) {
                stateMachine = HeadsetObjectsFactory.getInstance()
                        .makeStateMachine(device, mStateMachinesThread.getLooper(), this,
                                mAdapterService, mNativeInterface, mSystemInterface);
                stateMachine =
                        HeadsetObjectsFactory.getInstance()
                                .makeStateMachine(
                                        device,
                                        mStateMachinesLooper,
                                        this,
                                        mAdapterService,
                                        mNativeInterface,
                                        mSystemInterface);
                mStateMachines.put(device, stateMachine);
            }
            int connectionState = stateMachine.getConnectionState();
@@ -1064,7 +1083,7 @@ public class HeadsetService extends ProfileService {
                            + ", fall back to requesting device");
                    device = mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice;
                }
                getStateMachinesThreadHandler().removeCallbacks(mVoiceRecognitionTimeoutEvent);
                mStateMachinesThreadHandler.removeCallbacks(mVoiceRecognitionTimeoutEvent);
                mVoiceRecognitionTimeoutEvent = null;
                if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                    mSystemInterface.getVoiceRecognitionWakeLock().release();
@@ -1649,10 +1668,6 @@ public class HeadsetService extends ProfileService {
    public boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) {
        synchronized (mStateMachines) {
            Log.i(TAG, "dialOutgoingCall: from " + fromDevice);
            if (!isOnStateMachineThread()) {
                Log.e(TAG, "dialOutgoingCall must be called from state machine thread");
                return false;
            }
            if (mDialingOutTimeoutEvent != null) {
                Log.e(TAG, "dialOutgoingCall, already dialing by " + mDialingOutTimeoutEvent);
                return false;
@@ -1672,8 +1687,8 @@ public class HeadsetService extends ProfileService {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
            mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(fromDevice);
            getStateMachinesThreadHandler()
                    .postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
            mStateMachinesThreadHandler.postDelayed(
                    mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
            return true;
        }
    }
@@ -1768,8 +1783,8 @@ public class HeadsetService extends ProfileService {
                return false;
            }
            mVoiceRecognitionTimeoutEvent = new VoiceRecognitionTimeoutEvent(fromDevice);
            getStateMachinesThreadHandler()
                    .postDelayed(mVoiceRecognitionTimeoutEvent, sStartVrTimeoutMs);
            mStateMachinesThreadHandler.postDelayed(
                    mVoiceRecognitionTimeoutEvent, sStartVrTimeoutMs);

            if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                mSystemInterface.getVoiceRecognitionWakeLock().acquire(sStartVrTimeoutMs);
@@ -1798,8 +1813,7 @@ public class HeadsetService extends ProfileService {
                if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                    mSystemInterface.getVoiceRecognitionWakeLock().release();
                }
                getStateMachinesThreadHandler()
                        .removeCallbacks(mVoiceRecognitionTimeoutEvent);
                mStateMachinesThreadHandler.removeCallbacks(mVoiceRecognitionTimeoutEvent);

                mVoiceRecognitionTimeoutEvent = null;
            }
@@ -1848,8 +1862,7 @@ public class HeadsetService extends ProfileService {
            if (mDialingOutTimeoutEvent != null) {
                // Send result to state machine when dialing starts
                if (callState == HeadsetHalConstants.CALL_STATE_DIALING) {
                    getStateMachinesThreadHandler()
                            .removeCallbacks(mDialingOutTimeoutEvent);
                    mStateMachinesThreadHandler.removeCallbacks(mDialingOutTimeoutEvent);
                    doForStateMachine(mDialingOutTimeoutEvent.mDialingOutDevice,
                            stateMachine -> stateMachine.sendMessage(
                                    HeadsetStateMachine.DIALING_OUT_RESULT, 1 /* success */, 0,
@@ -1857,21 +1870,23 @@ public class HeadsetService extends ProfileService {
                } else if (callState == HeadsetHalConstants.CALL_STATE_ACTIVE
                        || callState == HeadsetHalConstants.CALL_STATE_IDLE) {
                    // Clear the timeout event when the call is connected or disconnected
                    if (!getStateMachinesThreadHandler()
                            .hasCallbacks(mDialingOutTimeoutEvent)) {
                    if (!mStateMachinesThreadHandler.hasCallbacks(mDialingOutTimeoutEvent)) {
                        mDialingOutTimeoutEvent = null;
                    }
                }
            }
        }
        getStateMachinesThreadHandler().post(() -> {
        mStateMachinesThreadHandler.post(
                () -> {
                    boolean isCallIdleBefore = mSystemInterface.isCallIdle();
                    mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
                    mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
                    mSystemInterface.getHeadsetPhoneState().setCallState(callState);
                    // Suspend A2DP when call about is about to become active
            if (mActiveDevice != null && callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED
                && !mSystemInterface.isCallIdle() && isCallIdleBefore
                    if (mActiveDevice != null
                            && callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED
                            && !mSystemInterface.isCallIdle()
                            && isCallIdleBefore
                            && !Utils.isScoManagedByAudioEnabled()) {
                        mSystemInterface.getAudioManager().setA2dpSuspended(true);
                        if (isAtLeastU()) {
@@ -1898,9 +1913,11 @@ public class HeadsetService extends ProfileService {
                }
            }
        }
        getStateMachinesThreadHandler().post(() -> {
        mStateMachinesThreadHandler.post(
                () -> {
                    if (callState == HeadsetHalConstants.CALL_STATE_IDLE
                && mSystemInterface.isCallIdle() && !isAudioOn()
                            && mSystemInterface.isCallIdle()
                            && !isAudioOn()
                            && !Utils.isScoManagedByAudioEnabled()) {
                        // Resume A2DP when call ended and SCO is not connected
                        mSystemInterface.getAudioManager().setA2dpSuspended(false);
@@ -2318,11 +2335,6 @@ public class HeadsetService extends ProfileService {
        }
    }

    private boolean isOnStateMachineThread() {
        final Looper myLooper = Looper.myLooper();
        return myLooper != null && (mStateMachinesThread != null) && (myLooper.getThread().getId()
                == mStateMachinesThread.getId());
    }

    /**
     * Retrieves the most recently connected device in the A2DP connected devices list.
@@ -2413,11 +2425,4 @@ public class HeadsetService extends ProfileService {
    private static void logD(String message) {
        Log.d(TAG, message);
    }

    private Handler getStateMachinesThreadHandler() {
        if (mStateMachinesThreadHandler == null) {
            mStateMachinesThreadHandler = new Handler(mStateMachinesThread.getLooper());
        }
        return mStateMachinesThreadHandler;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -1659,7 +1659,7 @@ class HeadsetStateMachine extends StateMachine {
     * @return device in focus
     */
    @VisibleForTesting
    public synchronized BluetoothDevice getDevice() {
    public BluetoothDevice getDevice() {
        return mDevice;
    }

+420 −391

File changed.

Preview size limit exceeded, changes collapsed.