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

Commit c4270127 authored by Hyundo Moon's avatar Hyundo Moon Committed by Gerrit Code Review
Browse files

Merge "Add PbapClientConnectionHandlerTest"

parents b9829535 e2fa87bc
Loading
Loading
Loading
Loading
+34 −29
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@ package com.android.bluetooth.pbapclient;

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

import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.ClientSession;
import com.android.obex.HeaderSet;
import com.android.obex.ResponseCodes;
@@ -102,7 +102,9 @@ class PbapClientConnectionHandler extends Handler {
    private static final long PBAP_REQUESTED_FIELDS =
            PBAP_FILTER_VERSION | PBAP_FILTER_FN | PBAP_FILTER_N | PBAP_FILTER_PHOTO
                    | 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 FAV_PATH = "telecom/fav.vcf";
@@ -126,7 +128,6 @@ class PbapClientConnectionHandler extends Handler {
    private Account mAccount;
    private AccountManager mAccountManager;
    private BluetoothSocket mSocket;
    private final BluetoothAdapter mAdapter;
    private final BluetoothDevice mDevice;
    // PSE SDP Record for current device.
    private SdpPseRecord mPseRec = null;
@@ -136,19 +137,6 @@ class PbapClientConnectionHandler extends Handler {
    private final PbapClientStateMachine mPbapClientStateMachine;
    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
     *
@@ -156,7 +144,6 @@ class PbapClientConnectionHandler extends Handler {
     */
    PbapClientConnectionHandler(Builder pceHandlerbuild) {
        super(pceHandlerbuild.mLooper);
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mDevice = pceHandlerbuild.mDevice;
        mContext = pceHandlerbuild.mContext;
        mPbapClientStateMachine = pceHandlerbuild.mClientStateMachine;
@@ -252,8 +239,8 @@ class PbapClientConnectionHandler extends Handler {
                if (DBG) {
                    Log.d(TAG, "Completing Disconnect");
                }
                removeAccount(mAccount);
                removeCallLog(mAccount);
                removeAccount();
                removeCallLog();

                mPbapClientStateMachine.sendMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED);
                break;
@@ -286,9 +273,20 @@ class PbapClientConnectionHandler extends Handler {
        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
     * channel, or RFCOMM default channel. */
    private synchronized boolean connectSocket() {
    @VisibleForTesting
    synchronized boolean connectSocket() {
        try {
            /* Use BluetoothSocket to connect */
            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
     * abstraction, then establish a Bluetooth Authenticator, and finally issue the connect call */
    private boolean connectObexSession() {
    @VisibleForTesting
    boolean connectObexSession() {
        boolean connectionSuccessful = false;

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

    public void abort() {
    void abort() {
        // 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.
        closeSocket();
@@ -385,6 +384,7 @@ class PbapClientConnectionHandler extends Handler {
        }
    }

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

    @VisibleForTesting
    void downloadCallLog(String path, HashMap<String, Integer> callCounter) {
        try {
            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 (DBG) {
                Log.d(TAG, "Added account " + mAccount);
@@ -463,17 +465,19 @@ class PbapClientConnectionHandler extends Handler {
        return false;
    }

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

    private void removeCallLog(Account account) {
    @VisibleForTesting
    void removeCallLog() {
        try {
            // need to check call table is exist ?
            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 (VDBG) Log.v(TAG, "No PBAP Server SDP Record");
            return false;
+252 −0
Original line number 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();
    }
}