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

Commit 6dc72243 authored by Sal Savage's avatar Sal Savage
Browse files

Clean up PbapClientServiceTest

This change:
- Reorganizes the test class by what's being tested so it's easier
  to follow along and see what's covered.
- Removes the BluetoothMethodProxy hack by using a MockContentResolver
  and MockContentProvider
- Changes a few test names to accurately reflect what they do now that
  there's not broadcast receiver for ACL state changes

Flag: EXEMPT, test only change
Bug: 365626536
Test: atest com.android.bluetooth.pbapclient
Change-Id: I5a954df17f0d54ea3cab24da9a6b3a857fcc2f64
parent abacae6b
Loading
Loading
Loading
Loading
+5 −12
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ import android.provider.CallLog;
import android.sysprop.BluetoothProperties;
import android.util.Log;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.R;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
@@ -297,11 +296,10 @@ public class PbapClientService extends ProfileService {
        }
    }

    private void removeHfpCallLog(String accountName, Context context) {
    private void removeHfpCallLog(String accountName) {
        Log.d(TAG, "Removing call logs from " + accountName);
        // Delete call logs belonging to accountName==BD_ADDR that also match
        // component name "hfpclient".
        ComponentName componentName = new ComponentName(context, HfpClientConnectionService.class);
        // Delete call logs belonging to accountName==BD_ADDR that also match component "hfpclient"
        ComponentName componentName = new ComponentName(this, HfpClientConnectionService.class);
        String selectionFilter =
                CallLog.Calls.PHONE_ACCOUNT_ID
                        + "=? AND "
@@ -309,12 +307,7 @@ public class PbapClientService extends ProfileService {
                        + "=?";
        String[] selectionArgs = new String[] {accountName, componentName.flattenToString()};
        try {
            BluetoothMethodProxy.getInstance()
                    .contentResolverDelete(
                            getContentResolver(),
                            CallLog.Calls.CONTENT_URI,
                            selectionFilter,
                            selectionArgs);
            getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs);
        } catch (IllegalArgumentException e) {
            Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
        }
@@ -345,7 +338,7 @@ public class PbapClientService extends ProfileService {
            Log.d(TAG, "Received intent to disconnect HFP with " + device);
            // HFP client stores entries in calllog.db by BD_ADDR and component name
            // Using the current Service as the context.
            removeHfpCallLog(device.getAddress(), this);
            removeHfpCallLog(device.getAddress());
        }
    }

+149 −86
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -27,19 +29,26 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.Intent;
import android.net.Uri;
import android.os.Looper;
import android.provider.CallLog;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;

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

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
@@ -62,47 +71,72 @@ public class PbapClientServiceTest {

    private PbapClientService mService = null;
    private BluetoothAdapter mAdapter = null;
    private Context mTargetContext;
    private BluetoothDevice mRemoteDevice;
    boolean mIsAdapterServiceSet;
    boolean mIsPbapClientServiceStarted;

    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock private AdapterService mAdapterService;

    @Mock private Context mMockContext;
    @Mock private AdapterService mMockAdapterService;
    @Mock private DatabaseManager mDatabaseManager;
    @Mock private PackageManager mMockPackageManager;
    private MockContentResolver mMockContentResolver;
    private MockCallLogProvider mMockCallLogProvider;
    @Mock private Resources mMockResources;
    @Mock private AccountManager mMockAccountManager;

    @Before
    public void setUp() throws Exception {
        mTargetContext = InstrumentationRegistry.getTargetContext();
        TestUtils.setAdapterService(mAdapterService);
        mIsAdapterServiceSet = true;
        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
        mService = new PbapClientService(mTargetContext);
        mService.start();
        mService.setAvailable(true);
        mIsPbapClientServiceStarted = true;
        // Try getting the Bluetooth adapter
        TestUtils.setAdapterService(mMockAdapterService);
        doReturn(mDatabaseManager).when(mMockAdapterService).getDatabase();

        doReturn("").when(mMockContext).getPackageName();
        doReturn(mMockPackageManager).when(mMockContext).getPackageManager();

        doReturn(mMockResources).when(mMockContext).getResources();
        doReturn(Utils.ACCOUNT_TYPE).when(mMockResources).getString(anyInt());

        mMockContentResolver = new MockContentResolver();
        mMockCallLogProvider = new MockCallLogProvider();
        mMockContentResolver.addProvider(CallLog.AUTHORITY, mMockCallLogProvider);
        doReturn(mMockContentResolver).when(mMockContext).getContentResolver();

        doReturn(AccountManager.VISIBILITY_VISIBLE)
                .when(mMockAccountManager)
                        .getAccountVisibility(any(Account.class), anyString());
        doReturn(new Account[]{})
                .when(mMockAccountManager)
                        .getAccountsByType(eq(Utils.ACCOUNT_TYPE));
        TestUtils.mockGetSystemService(
                mMockContext,
                Context.ACCOUNT_SERVICE,
                AccountManager.class,
                mMockAccountManager);

        mAdapter = BluetoothAdapter.getDefaultAdapter();
        Assert.assertNotNull(mAdapter);
        mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS);

        if (Looper.myLooper() == null) {
            Looper.prepare();
        }

        mService = new PbapClientService(mMockContext);
        mService.start();
        mService.setAvailable(true);
    }

    @After
    public void tearDown() throws Exception {
        if (!mIsAdapterServiceSet) {
            return;
        }
        if (mIsPbapClientServiceStarted) {
        if (mService != null) {
            mService.stop();
            mService = PbapClientService.getPbapClientService();
            Assert.assertNull(mService);
            mService = null;
        }
        TestUtils.clearAdapterService(mAdapterService);
        BluetoothMethodProxy.setInstanceForTesting(null);
        TestUtils.clearAdapterService(mMockAdapterService);
    }

    // *********************************************************************************************
    // * Initialize Service
    // *********************************************************************************************

    @Test
    public void testInitialize() {
        Assert.assertNotNull(PbapClientService.getPbapClientService());
@@ -115,17 +149,81 @@ public class PbapClientServiceTest {
        assertThat(PbapClientService.getPbapClientService()).isNull();
    }

    // *********************************************************************************************
    // * Incoming Events
    // *********************************************************************************************

    // ACL state changes from AdapterService

    @Test
    public void dump_callsStateMachineDump() {
    public void aclDisconnected_withLeTransport_doesNotCallDisconnect() {
        int connectionState = BluetoothProfile.STATE_CONNECTED;
        PbapClientStateMachine sm = mock(PbapClientStateMachine.class);
        mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm);
        StringBuilder builder = new StringBuilder();
        when(sm.getConnectionState(mRemoteDevice)).thenReturn(connectionState);

        mService.dump(builder);
        mService.aclDisconnected(mRemoteDevice, BluetoothDevice.TRANSPORT_LE);
        TestUtils.waitForLooperToFinishScheduledTask(Looper.getMainLooper());

        verify(sm).dump(builder);
        verify(sm, never()).disconnect(mRemoteDevice);
    }

    @Test
    public void aclDisconnected_withBrEdrTransport_callsDisconnect() {
        int connectionState = BluetoothProfile.STATE_CONNECTED;
        PbapClientStateMachine sm = mock(PbapClientStateMachine.class);
        mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm);
        when(sm.getConnectionState(mRemoteDevice)).thenReturn(connectionState);

        mService.aclDisconnected(mRemoteDevice, BluetoothDevice.TRANSPORT_BREDR);
        TestUtils.waitForLooperToFinishScheduledTask(Looper.getMainLooper());

        verify(sm).disconnect(mRemoteDevice);
    }

    // User unlock state changes

    @Test
    public void broadcastReceiver_withActionUserUnlocked_callsTryDownloadIfConnected() {
        PbapClientStateMachine sm = mock(PbapClientStateMachine.class);
        mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm);

        Intent intent = new Intent(Intent.ACTION_USER_UNLOCKED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
        mService.mPbapBroadcastReceiver.onReceive(mService, intent);

        verify(sm).tryDownloadIfConnected();
    }

    // HFP HF State changes

    @Test
    public void headsetClientConnectionStateChanged_hfpCallLogIsRemoved() {
        mService.handleHeadsetClientConnectionStateChanged(
                mRemoteDevice,
                BluetoothProfile.STATE_CONNECTED,
                BluetoothProfile.STATE_DISCONNECTED);

        assertThat(mMockCallLogProvider.getMostRecentlyDeletedDevice())
                .isEqualTo(mRemoteDevice.getAddress());
    }

    // Device state machines cleans up

    @Test
    public void cleanUpDevice() {
        PbapClientStateMachine sm = mock(PbapClientStateMachine.class);
        mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm);

        mService.cleanupDevice(mRemoteDevice);

        assertThat(mService.mPbapClientStateMachineMap).doesNotContainKey(mRemoteDevice);
    }

    // *********************************************************************************************
    // * API Methods
    // *********************************************************************************************

    @Test
    public void testSetConnectionPolicy_withNullDevice_throwsIAE() {
        assertThrows(
@@ -211,73 +309,38 @@ public class PbapClientServiceTest {
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    @Test
    public void cleanUpDevice() {
        PbapClientStateMachine sm = mock(PbapClientStateMachine.class);
        mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm);

        mService.cleanupDevice(mRemoteDevice);

        assertThat(mService.mPbapClientStateMachineMap).doesNotContainKey(mRemoteDevice);
    }
    // *********************************************************************************************
    // * Debug/Dump/toString()
    // *********************************************************************************************

    @Test
    public void broadcastReceiver_withActionAclDisconnectedLeTransport_doesNotCallDisconnect() {
        int connectionState = BluetoothProfile.STATE_CONNECTED;
    public void dump_callsStateMachineDump() {
        PbapClientStateMachine sm = mock(PbapClientStateMachine.class);
        mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm);
        when(sm.getConnectionState(mRemoteDevice)).thenReturn(connectionState);
        StringBuilder builder = new StringBuilder();

        mService.aclDisconnected(mRemoteDevice, BluetoothDevice.TRANSPORT_LE);
        TestUtils.waitForLooperToFinishScheduledTask(Looper.getMainLooper());
        mService.dump(builder);

        verify(sm, never()).disconnect(mRemoteDevice);
        verify(sm).dump(builder);
    }

    @Test
    public void broadcastReceiver_withActionAclDisconnectedBrEdrTransport_callsDisconnect() {
        int connectionState = BluetoothProfile.STATE_CONNECTED;
        PbapClientStateMachine sm = mock(PbapClientStateMachine.class);
        mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm);
        when(sm.getConnectionState(mRemoteDevice)).thenReturn(connectionState);
    // *********************************************************************************************
    // * Fake Call Log Provider
    // *********************************************************************************************

        mService.aclDisconnected(mRemoteDevice, BluetoothDevice.TRANSPORT_BREDR);
        TestUtils.waitForLooperToFinishScheduledTask(Looper.getMainLooper());
    private static class MockCallLogProvider extends MockContentProvider {
        private String mMostRecentlyDeletedDevice = null;

        verify(sm).disconnect(mRemoteDevice);
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            if (selectionArgs != null && selectionArgs.length > 0) {
                mMostRecentlyDeletedDevice = selectionArgs[0];
            }

    @Test
    public void broadcastReceiver_withActionUserUnlocked_callsTryDownloadIfConnected() {
        PbapClientStateMachine sm = mock(PbapClientStateMachine.class);
        mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm);

        Intent intent = new Intent(Intent.ACTION_USER_UNLOCKED);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
        mService.mPbapBroadcastReceiver.onReceive(mService, intent);

        verify(sm).tryDownloadIfConnected();
            return 0;
        }

    @Test
    public void headsetClientConnectionStateChanged_hfpCallLogIsRemoved() {
        BluetoothMethodProxy methodProxy = spy(BluetoothMethodProxy.getInstance());
        BluetoothMethodProxy.setInstanceForTesting(methodProxy);

        mService.handleHeadsetClientConnectionStateChanged(
                mRemoteDevice,
                BluetoothProfile.STATE_CONNECTED,
                BluetoothProfile.STATE_DISCONNECTED);

        ArgumentCaptor<Object> selectionArgsCaptor = ArgumentCaptor.forClass(Object.class);
        verify(methodProxy)
                .contentResolverDelete(
                        any(),
                        eq(CallLog.Calls.CONTENT_URI),
                        any(),
                        (String[]) selectionArgsCaptor.capture());

        assertThat(((String[]) selectionArgsCaptor.getValue())[0])
                .isEqualTo(mRemoteDevice.getAddress());
        public String getMostRecentlyDeletedDevice() {
            return mMostRecentlyDeletedDevice;
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -38,6 +38,9 @@ public class Utils {
    public static final String OUTGOING_CALL = "DIALED";
    private static final String CALL_HISTORY = "X-IRMC-CALL-DATETIME";

    public static final String ACCOUNT_TYPE =
            "com.android.bluetooth.pbapclient";

    /**
     * Group a list of VCard entries or Call History entries into a full phonebook
     *