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

Commit e2fa87bc authored by Hyundo Moon's avatar Hyundo Moon
Browse files

Add PbapClientConnectionHandlerTest

Bug: 237467631
Test: atest PbapClientConnectionHandlerTest
      (Need to enable pbapclient profile before test)
Change-Id: Ie0e893b34be608ffe1f2086058444086c910e21f
parent 893178e9
Loading
Loading
Loading
Loading
+34 −29
Original line number Original line Diff line number Diff line
@@ -17,7 +17,6 @@ package com.android.bluetooth.pbapclient;


import android.accounts.Account;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.BluetoothUuid;
@@ -32,6 +31,7 @@ import android.util.Log;


import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.R;
import com.android.bluetooth.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.ClientSession;
import com.android.obex.ClientSession;
import com.android.obex.HeaderSet;
import com.android.obex.HeaderSet;
import com.android.obex.ResponseCodes;
import com.android.obex.ResponseCodes;
@@ -102,7 +102,9 @@ class PbapClientConnectionHandler extends Handler {
    private static final long PBAP_REQUESTED_FIELDS =
    private static final long PBAP_REQUESTED_FIELDS =
            PBAP_FILTER_VERSION | PBAP_FILTER_FN | PBAP_FILTER_N | PBAP_FILTER_PHOTO
            PBAP_FILTER_VERSION | PBAP_FILTER_FN | PBAP_FILTER_N | PBAP_FILTER_PHOTO
                    | PBAP_FILTER_ADR | PBAP_FILTER_EMAIL | PBAP_FILTER_TEL | PBAP_FILTER_NICKNAME;
                    | PBAP_FILTER_ADR | PBAP_FILTER_EMAIL | PBAP_FILTER_TEL | PBAP_FILTER_NICKNAME;
    private static final int L2CAP_INVALID_PSM = -1;

    @VisibleForTesting
    static final int L2CAP_INVALID_PSM = -1;


    public static final String PB_PATH = "telecom/pb.vcf";
    public static final String PB_PATH = "telecom/pb.vcf";
    public static final String FAV_PATH = "telecom/fav.vcf";
    public static final String FAV_PATH = "telecom/fav.vcf";
@@ -126,7 +128,6 @@ class PbapClientConnectionHandler extends Handler {
    private Account mAccount;
    private Account mAccount;
    private AccountManager mAccountManager;
    private AccountManager mAccountManager;
    private BluetoothSocket mSocket;
    private BluetoothSocket mSocket;
    private final BluetoothAdapter mAdapter;
    private final BluetoothDevice mDevice;
    private final BluetoothDevice mDevice;
    // PSE SDP Record for current device.
    // PSE SDP Record for current device.
    private SdpPseRecord mPseRec = null;
    private SdpPseRecord mPseRec = null;
@@ -136,19 +137,6 @@ class PbapClientConnectionHandler extends Handler {
    private final PbapClientStateMachine mPbapClientStateMachine;
    private final PbapClientStateMachine mPbapClientStateMachine;
    private boolean mAccountCreated;
    private boolean mAccountCreated;


    PbapClientConnectionHandler(Looper looper, Context context, PbapClientStateMachine stateMachine,
            BluetoothDevice device) {
        super(looper);
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mDevice = device;
        mContext = context;
        mPbapClientStateMachine = stateMachine;
        mAuth = new BluetoothPbapObexAuthenticator(this);
        mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext());
        mAccount =
                new Account(mDevice.getAddress(), mContext.getString(R.string.pbap_account_type));
    }

    /**
    /**
     * Constructs PCEConnectionHandler object
     * Constructs PCEConnectionHandler object
     *
     *
@@ -156,7 +144,6 @@ class PbapClientConnectionHandler extends Handler {
     */
     */
    PbapClientConnectionHandler(Builder pceHandlerbuild) {
    PbapClientConnectionHandler(Builder pceHandlerbuild) {
        super(pceHandlerbuild.mLooper);
        super(pceHandlerbuild.mLooper);
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mDevice = pceHandlerbuild.mDevice;
        mDevice = pceHandlerbuild.mDevice;
        mContext = pceHandlerbuild.mContext;
        mContext = pceHandlerbuild.mContext;
        mPbapClientStateMachine = pceHandlerbuild.mClientStateMachine;
        mPbapClientStateMachine = pceHandlerbuild.mClientStateMachine;
@@ -252,8 +239,8 @@ class PbapClientConnectionHandler extends Handler {
                if (DBG) {
                if (DBG) {
                    Log.d(TAG, "Completing Disconnect");
                    Log.d(TAG, "Completing Disconnect");
                }
                }
                removeAccount(mAccount);
                removeAccount();
                removeCallLog(mAccount);
                removeCallLog();


                mPbapClientStateMachine.sendMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED);
                mPbapClientStateMachine.sendMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED);
                break;
                break;
@@ -286,9 +273,20 @@ class PbapClientConnectionHandler extends Handler {
        return;
        return;
    }
    }


    @VisibleForTesting
    synchronized void setPseRecord(SdpPseRecord record) {
        mPseRec = record;
    }

    @VisibleForTesting
    synchronized BluetoothSocket getSocket() {
        return mSocket;
    }

    /* Utilize SDP, if available, to create a socket connection over L2CAP, RFCOMM specified
    /* Utilize SDP, if available, to create a socket connection over L2CAP, RFCOMM specified
     * channel, or RFCOMM default channel. */
     * channel, or RFCOMM default channel. */
    private synchronized boolean connectSocket() {
    @VisibleForTesting
    synchronized boolean connectSocket() {
        try {
        try {
            /* Use BluetoothSocket to connect */
            /* Use BluetoothSocket to connect */
            if (mPseRec == null) {
            if (mPseRec == null) {
@@ -318,7 +316,8 @@ class PbapClientConnectionHandler extends Handler {


    /* Connect an OBEX session over the already connected socket.  First establish an OBEX Transport
    /* Connect an OBEX session over the already connected socket.  First establish an OBEX Transport
     * abstraction, then establish a Bluetooth Authenticator, and finally issue the connect call */
     * abstraction, then establish a Bluetooth Authenticator, and finally issue the connect call */
    private boolean connectObexSession() {
    @VisibleForTesting
    boolean connectObexSession() {
        boolean connectionSuccessful = false;
        boolean connectionSuccessful = false;


        try {
        try {
@@ -357,13 +356,13 @@ class PbapClientConnectionHandler extends Handler {
            // Will get NPE if a null mSocket is passed to BluetoothObexTransport.
            // Will get NPE if a null mSocket is passed to BluetoothObexTransport.
            // mSocket can be set to null if an abort() --> closeSocket() was called between
            // mSocket can be set to null if an abort() --> closeSocket() was called between
            // the calls to connectSocket() and connectObexSession().
            // the calls to connectSocket() and connectObexSession().
            Log.w(TAG, "CONNECT Failure " + e.toString());
            Log.w(TAG, "CONNECT Failure ", e);
            closeSocket();
            closeSocket();
        }
        }
        return connectionSuccessful;
        return connectionSuccessful;
    }
    }


    public void abort() {
    void abort() {
        // Perform forced cleanup, it is ok if the handler throws an exception this will free the
        // Perform forced cleanup, it is ok if the handler throws an exception this will free the
        // handler to complete what it is doing and finish with cleanup.
        // handler to complete what it is doing and finish with cleanup.
        closeSocket();
        closeSocket();
@@ -385,6 +384,7 @@ class PbapClientConnectionHandler extends Handler {
        }
        }
    }
    }


    @VisibleForTesting
    void downloadContacts(String path) {
    void downloadContacts(String path) {
        try {
        try {
            PhonebookPullRequest processor =
            PhonebookPullRequest processor =
@@ -438,6 +438,7 @@ class PbapClientConnectionHandler extends Handler {
        }
        }
    }
    }


    @VisibleForTesting
    void downloadCallLog(String path, HashMap<String, Integer> callCounter) {
    void downloadCallLog(String path, HashMap<String, Integer> callCounter) {
        try {
        try {
            BluetoothPbapRequestPullPhoneBook request =
            BluetoothPbapRequestPullPhoneBook request =
@@ -453,7 +454,8 @@ class PbapClientConnectionHandler extends Handler {
        }
        }
    }
    }


    private boolean addAccount(Account account) {
    @VisibleForTesting
    boolean addAccount(Account account) {
        if (mAccountManager.addAccountExplicitly(account, null, null)) {
        if (mAccountManager.addAccountExplicitly(account, null, null)) {
            if (DBG) {
            if (DBG) {
                Log.d(TAG, "Added account " + mAccount);
                Log.d(TAG, "Added account " + mAccount);
@@ -463,17 +465,19 @@ class PbapClientConnectionHandler extends Handler {
        return false;
        return false;
    }
    }


    private void removeAccount(Account account) {
    @VisibleForTesting
        if (mAccountManager.removeAccountExplicitly(account)) {
    void removeAccount() {
        if (mAccountManager.removeAccountExplicitly(mAccount)) {
            if (DBG) {
            if (DBG) {
                Log.d(TAG, "Removed account " + account);
                Log.d(TAG, "Removed account " + mAccount);
            }
            }
        } else {
        } else {
            Log.e(TAG, "Failed to remove account " + mAccount);
            Log.e(TAG, "Failed to remove account " + mAccount);
        }
        }
    }
    }


    private void removeCallLog(Account account) {
    @VisibleForTesting
    void removeCallLog() {
        try {
        try {
            // need to check call table is exist ?
            // need to check call table is exist ?
            if (mContext.getContentResolver() == null) {
            if (mContext.getContentResolver() == null) {
@@ -489,7 +493,8 @@ class PbapClientConnectionHandler extends Handler {
        }
        }
    }
    }


    private boolean isRepositorySupported(int mask) {
    @VisibleForTesting
    boolean isRepositorySupported(int mask) {
        if (mPseRec == null) {
        if (mPseRec == null) {
            if (VDBG) Log.v(TAG, "No PBAP Server SDP Record");
            if (VDBG) Log.v(TAG, "No PBAP Server SDP Record");
            return false;
            return false;
+252 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.bluetooth.pbapclient;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.accounts.Account;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.SdpPseRecord;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;

import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.storage.DatabaseManager;

import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.HashMap;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class PbapClientConnectionHandlerTest {

    private static final String TAG = "ConnHandlerTest";
    private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00";

    private HandlerThread mThread;
    private Looper mLooper;
    private Context mTargetContext;
    private BluetoothDevice mRemoteDevice;

    @Rule
    public final ServiceTestRule mServiceRule = new ServiceTestRule();

    @Mock
    private AdapterService mAdapterService;

    @Mock
    private DatabaseManager mDatabaseManager;

    private BluetoothAdapter mAdapter;

    private PbapClientService mService;

    private PbapClientStateMachine mStateMachine;

    private PbapClientConnectionHandler mHandler;

    @Before
    public void setUp() throws Exception {
        mTargetContext = spy(new ContextWrapper(
                InstrumentationRegistry.getInstrumentation().getTargetContext()));
        Assume.assumeTrue("Ignore test when PbapClientService is not enabled",
                PbapClientService.isEnabled());
        MockitoAnnotations.initMocks(this);
        TestUtils.setAdapterService(mAdapterService);
        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
        doReturn(true, false).when(mAdapterService)
                .isStartedProfile(anyString());
        TestUtils.startService(mServiceRule, PbapClientService.class);
        mService = PbapClientService.getPbapClientService();
        assertThat(mService).isNotNull();

        mAdapter = BluetoothAdapter.getDefaultAdapter();

        mThread = new HandlerThread("test_handler_thread");
        mThread.start();
        mLooper = mThread.getLooper();
        mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS);

        mStateMachine = new PbapClientStateMachine(mService, mRemoteDevice);
        mHandler = new PbapClientConnectionHandler.Builder()
                .setLooper(mLooper)
                .setClientSM(mStateMachine)
                .setContext(mTargetContext)
                .setRemoteDevice(mRemoteDevice)
                .build();
    }

    @After
    public void tearDown() throws Exception {
        if (!PbapClientService.isEnabled()) {
            return;
        }
        TestUtils.stopService(mServiceRule, PbapClientService.class);
        mService = PbapClientService.getPbapClientService();
        assertThat(mService).isNull();
        TestUtils.clearAdapterService(mAdapterService);
        mLooper.quit();
    }

    @Test
    public void connectSocket_whenBluetoothIsNotEnabled_returnsFalse() {
        assertThat(mHandler.connectSocket()).isFalse();
    }

    @Test
    public void connectSocket_whenBluetoothIsNotEnabled_returnsFalse_withInvalidL2capPsm() {
        SdpPseRecord record = mock(SdpPseRecord.class);
        mHandler.setPseRecord(record);

        when(record.getL2capPsm()).thenReturn(PbapClientConnectionHandler.L2CAP_INVALID_PSM);
        assertThat(mHandler.connectSocket()).isFalse();
    }

    @Test
    public void connectSocket_whenBluetoothIsNotEnabled_returnsFalse_withValidL2capPsm() {
        SdpPseRecord record = mock(SdpPseRecord.class);
        mHandler.setPseRecord(record);

        when(record.getL2capPsm()).thenReturn(1); // Valid PSM ranges 1 to 30;
        assertThat(mHandler.connectSocket()).isFalse();
    }

    // TODO: Add connectObexSession_returnsTrue

    @Test
    public void connectObexSession_returnsFalse_withoutConnectingSocket() {
        assertThat(mHandler.connectObexSession()).isFalse();
    }

    @Test
    public void abort() {
        SdpPseRecord record = mock(SdpPseRecord.class);
        when(record.getL2capPsm()).thenReturn(1); // Valid PSM ranges 1 to 30;
        mHandler.setPseRecord(record);
        mHandler.connectSocket(); // Workaround for setting mSocket as non-null value
        assertThat(mHandler.getSocket()).isNotNull();

        mHandler.abort();

        assertThat(mThread.isInterrupted()).isTrue();
        assertThat(mHandler.getSocket()).isNull();
    }

    @Test
    public void downloadContacts() {
        final String path = PbapClientConnectionHandler.PB_PATH;

        try {
            mHandler.downloadContacts(path);
        } catch (Exception e) {
            Log.e(TAG, "Exception happened.", e);
            assertWithMessage("Exception should not be thrown!").fail();
        }
    }

    @Test
    public void downloadCallLog() {
        final String path = PbapClientConnectionHandler.ICH_PATH;
        final HashMap<String, Integer> callCounter = new HashMap<>();

        try {
            mHandler.downloadCallLog(path, callCounter);
        } catch (Exception e) {
            Log.e(TAG, "Exception happened.", e);
            assertWithMessage("Exception should not be thrown!").fail();
        }
    }

    @Test
    public void addAccount() {
        try {
            mHandler.addAccount(mock(Account.class));
        } catch (Exception e) {
            Log.e(TAG, "Exception happened.", e);
            assertWithMessage("Exception should not be thrown!").fail();
        }
    }

    @Test
    public void removeAccount() {
        try {
            mHandler.removeAccount();
        } catch (Exception e) {
            Log.e(TAG, "Exception happened.", e);
            assertWithMessage("Exception should not be thrown!").fail();
        }
    }

    @Test
    public void removeCallLog() {
        try {
            ContentResolver res = mock(ContentResolver.class);
            when(mTargetContext.getContentResolver()).thenReturn(res);
            mHandler.removeCallLog();

            when(mTargetContext.getContentResolver()).thenReturn(null);
            mHandler.removeCallLog();
        } catch (Exception e) {
            Log.e(TAG, "Exception happened.", e);
            assertWithMessage("Exception should not be thrown!").fail();
        }
    }

    @Test
    public void isRepositorySupported_withoutSettingPseRecord_returnsFalse() {
        mHandler.setPseRecord(null);
        final int mask = 0x11;

        assertThat(mHandler.isRepositorySupported(mask)).isFalse();
    }

    @Test
    public void isRepositorySupported_withSettingPseRecord() {
        SdpPseRecord record = mock(SdpPseRecord.class);
        when(record.getSupportedRepositories()).thenReturn(1);
        mHandler.setPseRecord(record);
        final int mask = 0x11;

        assertThat(mHandler.isRepositorySupported(mask)).isTrue();
    }
}