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

Commit 2fa9276a authored by Sungsoo Lim's avatar Sungsoo Lim Committed by Gerrit Code Review
Browse files

Merge changes I7b49676a,I88fa4f3b into main

* changes:
  Use test looper to prevent unexpected handler executions
  Prevent unexpected removal all messages
parents 5e355ae4 d28a91ff
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.provider.Telephony;
@@ -263,4 +265,9 @@ public class BluetoothMethodProxy {
    public void threadStart(Thread thread) {
        thread.start();
    }

    /** Proxies {@link HandlerThread#getLooper()}. */
    public Looper handlerThreadGetLooper(HandlerThread handlerThread) {
        return handlerThread.getLooper();
    }
}
+12 −7
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.os.Looper;
import android.util.ArraySet;
import android.util.Log;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
@@ -273,8 +274,9 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                        Log.d(TAG, "A2DP activation is suspended until HFP connected: "
                                + device);
                    }

                    if (mPendingActiveDevice != null) {
                        mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                    }
                    mPendingActiveDevice = device;
                    // Activate A2DP if HFP is failed to connect.
                    mHandler.postDelayed(
@@ -346,7 +348,9 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                        Log.d(TAG, "HFP activation is suspended until A2DP connected: "
                                + device);
                    }
                    if (mPendingActiveDevice != null) {
                        mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                    }
                    mPendingActiveDevice = device;
                    // Activate HFP if A2DP is failed to connect.
                    mHandler.postDelayed(
@@ -773,8 +777,9 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
        }

        mHandlerThread = new HandlerThread("BluetoothActiveDeviceManager");
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        BluetoothMethodProxy mp = BluetoothMethodProxy.getInstance();
        mp.threadStart(mHandlerThread);
        mHandler = new Handler(mp.handlerThreadGetLooper(mHandlerThread));

        mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);
        mAdapterService.registerBluetoothStateCallback((command) -> mHandler.post(command), this);
@@ -802,10 +807,10 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
     */
    @VisibleForTesting
    public Looper getHandlerLooper() {
        if (mHandlerThread == null) {
        if (mHandler == null) {
            return null;
        }
        return mHandlerThread.getLooper();
        return mHandler.getLooper();
    }

    private boolean setA2dpActiveDevice(@NonNull BluetoothDevice device) {
+12 −7
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.os.Looper;
import android.util.ArraySet;
import android.util.Log;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
@@ -236,8 +237,9 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                    if (DBG) {
                        Log.d(TAG, "A2DP activation is suspended until HFP connected: " + device);
                    }

                    if (mPendingActiveDevice != null) {
                        mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                    }
                    mPendingActiveDevice = device;
                    // Activate A2DP if HFP is failed to connect.
                    mHandler.postDelayed(
@@ -307,7 +309,9 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                    if (DBG) {
                        Log.d(TAG, "HFP activation is suspended until A2DP connected: " + device);
                    }
                    if (mPendingActiveDevice != null) {
                        mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                    }
                    mPendingActiveDevice = device;
                    // Activate HFP if A2DP is failed to connect.
                    mHandler.postDelayed(
@@ -773,8 +777,9 @@ public class AudioRoutingManager extends ActiveDeviceManager {
        }

        mHandlerThread = new HandlerThread("BluetoothActiveDeviceManager");
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        BluetoothMethodProxy mp = BluetoothMethodProxy.getInstance();
        mp.threadStart(mHandlerThread);
        mHandler = new Handler(mp.handlerThreadGetLooper(mHandlerThread));

        mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);
        mAdapterService.registerBluetoothStateCallback((command) -> mHandler.post(command), this);
@@ -803,10 +808,10 @@ public class AudioRoutingManager extends ActiveDeviceManager {
    @VisibleForTesting
    @Override
    public Looper getHandlerLooper() {
        if (mHandlerThread == null) {
        if (mHandler == null) {
            return null;
        }
        return mHandlerThread.getLooper();
        return mHandler.getLooper();
    }

    private boolean setA2dpActiveDevice(@NonNull BluetoothDevice device) {
+4 −3
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import android.sysprop.BluetoothProperties;
import android.telephony.TelephonyManager;
import android.util.Log;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.IObexConnectionHandler;
import com.android.bluetooth.ObexServerSockets;
import com.android.bluetooth.R;
@@ -468,7 +469,6 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
                sNotificationManager.cancelAll();
            }
        }

    }
    private class PbapHandler extends Handler {
        private PbapHandler(Looper looper) {
@@ -710,8 +710,9 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect
        mContext = this;
        mContactsLoaded = false;
        mHandlerThread = new HandlerThread("PbapHandlerThread");
        mHandlerThread.start();
        mSessionStatusHandler = new PbapHandler(mHandlerThread.getLooper());
        BluetoothMethodProxy mp = BluetoothMethodProxy.getInstance();
        mp.threadStart(mHandlerThread);
        mSessionStatusHandler = new PbapHandler(mp.handlerThreadGetLooper(mHandlerThread));
        IntentFilter filter = new IntentFilter();
        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
+48 −11
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -36,12 +38,14 @@ import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothSinkAudioPolicy;
import android.content.Context;
import android.media.AudioManager;
import android.os.test.TestLooper;
import android.util.ArrayMap;
import android.util.SparseIntArray;

import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
@@ -61,6 +65,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import java.util.ArrayList;
import java.util.List;
@@ -89,6 +94,7 @@ public class ActiveDeviceManagerTest {
            ActiveDeviceManager.A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS + 2_000;
    private boolean mOriginalDualModeAudioState;
    private TestDatabaseManager mDatabaseManager;
    private TestLooper mTestLooper;

    @Mock private AdapterService mAdapterService;
    @Mock private ServiceFactory mServiceFactory;
@@ -97,15 +103,20 @@ public class ActiveDeviceManagerTest {
    @Mock private HearingAidService mHearingAidService;
    @Mock private LeAudioService mLeAudioService;
    @Mock private AudioManager mAudioManager;
    @Spy private BluetoothMethodProxy mMethodProxy = BluetoothMethodProxy.getInstance();

    @Before
    public void setUp() throws Exception {
        // Set up mocks and test assets
        MockitoAnnotations.initMocks(this);
        mTestLooper = new TestLooper();
        BluetoothMethodProxy.setInstanceForTesting(mMethodProxy);
        doReturn(mTestLooper.getLooper()).when(mMethodProxy).handlerThreadGetLooper(any());
        doNothing().when(mMethodProxy).threadStart(any());
        mTestLooper.startAutoDispatch();
        TestUtils.setAdapterService(mAdapterService);

        mDatabaseManager = new TestDatabaseManager(mAdapterService, new FakeFeatureFlagsImpl());

        when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
        when(mAdapterService.getSystemServiceName(AudioManager.class))
                .thenReturn(Context.AUDIO_SERVICE);
@@ -165,6 +176,8 @@ public class ActiveDeviceManagerTest {

    @After
    public void tearDown() throws Exception {
        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
        BluetoothMethodProxy.setInstanceForTesting(null);
        mActiveDeviceManager.cleanup();
        TestUtils.clearAdapterService(mAdapterService);
        Utils.setDualModeAudioStateForTesting(mOriginalDualModeAudioState);
@@ -193,6 +206,18 @@ public class ActiveDeviceManagerTest {
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
    }

    @Test
    public void a2dpAndHfpConnectedAtTheSameTime_setA2dpActiveShouldBeCalled() {
        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        a2dpConnected(mA2dpHeadsetDevice, true);
        headsetConnected(mA2dpHeadsetDevice, true);
        mTestLooper.dispatchAll();
        verify(mA2dpService).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService).setActiveDevice(mA2dpHeadsetDevice);
    }

    /**
     * Two A2DP are connected. Should set the second one active.
     */
@@ -324,20 +349,30 @@ public class ActiveDeviceManagerTest {

    @Test
    public void a2dpConnectedButHeadsetNotConnected_setA2dpActive() {
        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        a2dpConnected(mA2dpHeadsetDevice, true);
        verify(mA2dpService, timeout(A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS))
                .setActiveDevice(mA2dpHeadsetDevice);

        mTestLooper.moveTimeForward(ActiveDeviceManager.A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS / 2);
        mTestLooper.dispatchAll();
        verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice);
        mTestLooper.moveTimeForward(A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS);
        mTestLooper.dispatchAll();
        verify(mA2dpService).setActiveDevice(mA2dpHeadsetDevice);
    }

    @Test
    public void headsetConnectedButA2dpNotConnected_setHeadsetActive() {
        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        headsetConnected(mA2dpHeadsetDevice, true);
        verify(mHeadsetService, timeout(A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS))
                .setActiveDevice(mA2dpHeadsetDevice);

        mTestLooper.moveTimeForward(ActiveDeviceManager.A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS / 2);
        mTestLooper.dispatchAll();
        verify(mHeadsetService, never()).setActiveDevice(mA2dpHeadsetDevice);
        mTestLooper.moveTimeForward(A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS);
        mTestLooper.dispatchAll();
        verify(mHeadsetService).setActiveDevice(mA2dpHeadsetDevice);
    }

    @Test
@@ -367,25 +402,27 @@ public class ActiveDeviceManagerTest {

    @Test
    public void hfpActivatedAfterTimeout_shouldActivateA2dpAgain() {
        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
        a2dpConnected(mA2dpHeadsetDevice, true);
        headsetConnected(mA2dpHeadsetDevice, true);
        a2dpActiveDeviceChanged(null);
        headsetActiveDeviceChanged(null);

        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        mTestLooper.dispatchAll();
        Mockito.clearInvocations(mHeadsetService);
        Mockito.clearInvocations(mA2dpService);

        // When A2DP is activated, then it should activate HFP
        a2dpActiveDeviceChanged(mA2dpHeadsetDevice);
        verify(mA2dpService, after(A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS).never())
                .setActiveDevice(any());
        mTestLooper.moveTimeForward(A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS);
        mTestLooper.dispatchAll();
        verify(mA2dpService, never()).setActiveDevice(any());
        verify(mHeadsetService).setActiveDevice(mA2dpHeadsetDevice);

        a2dpActiveDeviceChanged(null);
        // When HFP activated after timeout, it should activate A2DP again
        headsetActiveDeviceChanged(mA2dpHeadsetDevice);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        mTestLooper.dispatchAll();
        verify(mA2dpService).setActiveDevice(mA2dpHeadsetDevice);
    }

Loading