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