From 8311d24a631cf1c4e78c4b236a080e2d2ac2be59 Mon Sep 17 00:00:00 2001 From: Hyundo Moon <hdmoon@google.com> Date: Sat, 17 Dec 2022 21:49:19 +0900 Subject: [PATCH] Add PbapClientServiceTest Bug: 237467631 Test: atest PbapClientServiceTest Change-Id: I6c80d67b8fb973476420a08f6ce1a6d09947601d (cherry picked from commit 98771679eeb3a03b6e2d55d9ef5294fc6704f9e0) Merged-In: I6c80d67b8fb973476420a08f6ce1a6d09947601d --- .../pbapclient/PbapClientService.java | 28 +- .../pbapclient/PbapClientStateMachine.java | 2 +- .../pbapclient/PbapClientServiceTest.java | 272 +++++++++++++++++- 3 files changed, 292 insertions(+), 10 deletions(-) diff --git a/android/app/src/com/android/bluetooth/pbapclient/PbapClientService.java b/android/app/src/com/android/bluetooth/pbapclient/PbapClientService.java index 5d472200755..ca24aacd81d 100644 --- a/android/app/src/com/android/bluetooth/pbapclient/PbapClientService.java +++ b/android/app/src/com/android/bluetooth/pbapclient/PbapClientService.java @@ -34,6 +34,7 @@ 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.Utils; import com.android.bluetooth.btservice.AdapterService; @@ -41,6 +42,7 @@ import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.hfpclient.HfpClientConnectionService; import com.android.bluetooth.sdp.SdpManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.SynchronousResultReceiver; import java.util.ArrayList; @@ -69,10 +71,12 @@ public class PbapClientService extends ProfileService { // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices. private static final int MAXIMUM_DEVICES = 10; - private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap = + @VisibleForTesting + Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap = new ConcurrentHashMap<>(); private static PbapClientService sPbapClientService; - private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver(); + @VisibleForTesting + PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver(); private int mSdpHandle = -1; private DatabaseManager mDatabaseManager; @@ -162,6 +166,7 @@ public class PbapClientService extends ProfileService { for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) { pbapClientStateMachine.doQuit(); } + mPbapClientStateMachineMap.clear(); cleanupAuthenicationService(); setComponentAvailable(AUTHENTICATOR_SERVICE, false); return true; @@ -243,7 +248,8 @@ public class PbapClientService extends ProfileService { + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=?"; String[] selectionArgs = new String[]{accountName, componentName.flattenToString()}; try { - getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs); + BluetoothMethodProxy.getInstance().contentResolverDelete(getContentResolver(), + CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs); } catch (IllegalArgumentException e) { Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); } @@ -278,7 +284,8 @@ public class PbapClientService extends ProfileService { } - private class PbapBroadcastReceiver extends BroadcastReceiver { + @VisibleForTesting + class PbapBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -314,7 +321,8 @@ public class PbapClientService extends ProfileService { /** * Handler for incoming service calls */ - private static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub + @VisibleForTesting + static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub implements IProfileServiceBinder { private PbapClientService mService; @@ -329,6 +337,9 @@ public class PbapClientService extends ProfileService { @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private PbapClientService getService(AttributionSource source) { + if (Utils.isInstrumentationTestMode()) { + return mService; + } if (!Utils.checkServiceAvailable(mService, TAG) || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG) || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { @@ -461,7 +472,8 @@ public class PbapClientService extends ProfileService { return sPbapClientService; } - private static synchronized void setPbapClientService(PbapClientService instance) { + @VisibleForTesting + static synchronized void setPbapClientService(PbapClientService instance) { if (VDBG) { Log.v(TAG, "setPbapClientService(): set to: " + instance); } @@ -511,7 +523,6 @@ public class PbapClientService extends ProfileService { if (pbapClientStateMachine != null) { pbapClientStateMachine.disconnect(device); return true; - } else { Log.w(TAG, "disconnect() called on unconnected device."); return false; @@ -523,7 +534,8 @@ public class PbapClientService extends ProfileService { return getDevicesMatchingConnectionStates(desiredStates); } - private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + @VisibleForTesting + List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0); for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry : mPbapClientStateMachineMap diff --git a/android/app/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java b/android/app/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java index 6f4c0fab6c3..8331c634bd6 100644 --- a/android/app/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java +++ b/android/app/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java @@ -70,7 +70,7 @@ import com.android.internal.util.StateMachine; import java.util.ArrayList; import java.util.List; -final class PbapClientStateMachine extends StateMachine { +class PbapClientStateMachine extends StateMachine { private static final boolean DBG = false; //Utils.DBG; private static final String TAG = "PbapClientStateMachine"; diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java index 4ddd9f2d79e..53aaeb07580 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java @@ -15,21 +15,36 @@ */ package com.android.bluetooth.pbapclient; +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.anyString; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadsetClient; +import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.content.Intent; +import android.provider.CallLog; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; -import com.android.bluetooth.R; +import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.storage.DatabaseManager; +import com.android.bluetooth.x.com.android.modules.utils.SynchronousResultReceiver; import org.junit.After; import org.junit.Assert; @@ -38,15 +53,19 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @MediumTest @RunWith(AndroidJUnit4.class) public class PbapClientServiceTest { + private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00"; + private PbapClientService mService = null; private BluetoothAdapter mAdapter = null; private Context mTargetContext; + private BluetoothDevice mRemoteDevice; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @@ -69,6 +88,7 @@ public class PbapClientServiceTest { // Try getting the Bluetooth adapter mAdapter = BluetoothAdapter.getDefaultAdapter(); Assert.assertNotNull(mAdapter); + mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS); } @After @@ -80,10 +100,260 @@ public class PbapClientServiceTest { mService = PbapClientService.getPbapClientService(); Assert.assertNull(mService); TestUtils.clearAdapterService(mAdapterService); + BluetoothMethodProxy.setInstanceForTesting(null); } @Test public void testInitialize() { Assert.assertNotNull(PbapClientService.getPbapClientService()); } + + @Test + public void testSetPbapClientService_withNull() { + PbapClientService.setPbapClientService(null); + + assertThat(PbapClientService.getPbapClientService()).isNull(); + } + + @Test + public void dump_callsStateMachineDump() { + PbapClientStateMachine sm = mock(PbapClientStateMachine.class); + mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm); + StringBuilder builder = new StringBuilder(); + + mService.dump(builder); + + verify(sm).dump(builder); + } + + @Test + public void testSetConnectionPolicy_withNullDevice_throwsIAE() { + assertThrows(IllegalArgumentException.class, () -> mService.setConnectionPolicy( + null, BluetoothProfile.CONNECTION_POLICY_ALLOWED)); + } + + @Test + public void testSetConnectionPolicy() { + int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + when(mDatabaseManager.setProfileConnectionPolicy( + mRemoteDevice, BluetoothProfile.PBAP_CLIENT, connectionPolicy)).thenReturn(true); + + assertThat(mService.setConnectionPolicy(mRemoteDevice, connectionPolicy)).isTrue(); + } + + @Test + public void testGetConnectionPolicy_withNullDevice_throwsIAE() { + assertThrows(IllegalArgumentException.class, () -> mService.getConnectionPolicy(null)); + } + + @Test + public void testGetConnectionPolicy() { + int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_ALLOWED; + when(mDatabaseManager.getProfileConnectionPolicy( + mRemoteDevice, BluetoothProfile.PBAP_CLIENT)).thenReturn(connectionPolicy); + + assertThat(mService.getConnectionPolicy(mRemoteDevice)).isEqualTo(connectionPolicy); + } + + @Test + public void testConnect_withNullDevice_throwsIAE() { + assertThrows(IllegalArgumentException.class, () -> mService.connect(null)); + } + + @Test + public void testConnect_whenPolicyIsForbidden_returnsFalse() { + int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + when(mDatabaseManager.getProfileConnectionPolicy( + mRemoteDevice, BluetoothProfile.PBAP_CLIENT)).thenReturn(connectionPolicy); + + assertThat(mService.connect(mRemoteDevice)).isFalse(); + } + + @Test + public void testConnect_whenPolicyIsAllowed_returnsTrue() { + int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_ALLOWED; + when(mDatabaseManager.getProfileConnectionPolicy( + mRemoteDevice, BluetoothProfile.PBAP_CLIENT)).thenReturn(connectionPolicy); + + assertThat(mService.connect(mRemoteDevice)).isTrue(); + } + + @Test + public void testDisconnect_withNullDevice_throwsIAE() { + assertThrows(IllegalArgumentException.class, () -> mService.disconnect(null)); + } + + @Test + public void testDisconnect_whenNotConnected_returnsFalse() { + assertThat(mService.disconnect(mRemoteDevice)).isFalse(); + } + + @Test + public void testDisconnect_whenConnected_returnsTrue() { + PbapClientStateMachine sm = mock(PbapClientStateMachine.class); + mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm); + + assertThat(mService.disconnect(mRemoteDevice)).isTrue(); + + verify(sm).disconnect(mRemoteDevice); + } + + @Test + public void testGetConnectionState_whenNotConnected() { + assertThat(mService.getConnectionState(mRemoteDevice)) + .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); + } + + @Test + public void getConnectedDevices() { + int connectionState = BluetoothProfile.STATE_CONNECTED; + PbapClientStateMachine sm = mock(PbapClientStateMachine.class); + mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm); + when(sm.getConnectionState()).thenReturn(connectionState); + + assertThat(mService.getConnectedDevices()).contains(mRemoteDevice); + } + + @Test + public void binder_connect_callsServiceMethod() { + PbapClientService mockService = mock(PbapClientService.class); + PbapClientService.BluetoothPbapClientBinder binder = + new PbapClientService.BluetoothPbapClientBinder(mockService); + + binder.connect(mRemoteDevice, null, SynchronousResultReceiver.get()); + + verify(mockService).connect(mRemoteDevice); + } + + @Test + public void binder_disconnect_callsServiceMethod() { + PbapClientService mockService = mock(PbapClientService.class); + PbapClientService.BluetoothPbapClientBinder binder = + new PbapClientService.BluetoothPbapClientBinder(mockService); + + binder.disconnect(mRemoteDevice, null, SynchronousResultReceiver.get()); + + verify(mockService).disconnect(mRemoteDevice); + } + + @Test + public void binder_getConnectedDevices_callsServiceMethod() { + PbapClientService mockService = mock(PbapClientService.class); + PbapClientService.BluetoothPbapClientBinder binder = + new PbapClientService.BluetoothPbapClientBinder(mockService); + + binder.getConnectedDevices(null, SynchronousResultReceiver.get()); + + verify(mockService).getConnectedDevices(); + } + + @Test + public void binder_getDevicesMatchingConnectionStates_callsServiceMethod() { + PbapClientService mockService = mock(PbapClientService.class); + PbapClientService.BluetoothPbapClientBinder binder = + new PbapClientService.BluetoothPbapClientBinder(mockService); + + int[] states = new int[] {BluetoothProfile.STATE_CONNECTED}; + binder.getDevicesMatchingConnectionStates(states, null, SynchronousResultReceiver.get()); + + verify(mockService).getDevicesMatchingConnectionStates(states); + } + + @Test + public void binder_getConnectionState_callsServiceMethod() { + PbapClientService mockService = mock(PbapClientService.class); + PbapClientService.BluetoothPbapClientBinder binder = + new PbapClientService.BluetoothPbapClientBinder(mockService); + + binder.getConnectionState(mRemoteDevice, null, SynchronousResultReceiver.get()); + + verify(mockService).getConnectionState(mRemoteDevice); + } + + @Test + public void binder_setConnectionPolicy_callsServiceMethod() { + PbapClientService mockService = mock(PbapClientService.class); + PbapClientService.BluetoothPbapClientBinder binder = + new PbapClientService.BluetoothPbapClientBinder(mockService); + + int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_ALLOWED; + binder.setConnectionPolicy(mRemoteDevice, connectionPolicy, + null, SynchronousResultReceiver.get()); + + verify(mockService).setConnectionPolicy(mRemoteDevice, connectionPolicy); + } + + @Test + public void binder_getConnectionPolicy_callsServiceMethod() { + PbapClientService mockService = mock(PbapClientService.class); + PbapClientService.BluetoothPbapClientBinder binder = + new PbapClientService.BluetoothPbapClientBinder(mockService); + + binder.getConnectionPolicy(mRemoteDevice, null, SynchronousResultReceiver.get()); + + verify(mockService).getConnectionPolicy(mRemoteDevice); + } + + @Test + public void binder_cleanUp_doesNotCrash() { + PbapClientService mockService = mock(PbapClientService.class); + PbapClientService.BluetoothPbapClientBinder binder = + new PbapClientService.BluetoothPbapClientBinder(mockService); + + binder.cleanup(); + } + + @Test + public void broadcastReceiver_withActionAclDisconnected_callsDisconnect() { + int connectionState = BluetoothProfile.STATE_CONNECTED; + PbapClientStateMachine sm = mock(PbapClientStateMachine.class); + mService.mPbapClientStateMachineMap.put(mRemoteDevice, sm); + when(sm.getConnectionState(mRemoteDevice)).thenReturn(connectionState); + + Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); + mService.mPbapBroadcastReceiver.onReceive(mService, intent); + + verify(sm).disconnect(mRemoteDevice); + } + + @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(); + } + + @Test + public void broadcastReceiver_withActionHeadsetClientConnectionStateChanged() { + BluetoothMethodProxy methodProxy = spy(BluetoothMethodProxy.getInstance()); + BluetoothMethodProxy.setInstanceForTesting(methodProxy); + + Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); + intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); + mService.mPbapBroadcastReceiver.onReceive(mService, intent); + + 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()); + } } -- GitLab