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

Commit 9fe5c133 authored by Sal Savage's avatar Sal Savage
Browse files

Create PbapSdpRecord wrapper for record and constants

Bug: 365626536
Flag: EXEMPT, mechanical refactor, no logic change + tests
Test: atest com.android.bluetooth.pbapclient
Test: m com.android.btservices
Change-Id: I09bb514a96155247891218a296339394ffb2dee3
parent 2c8afeb9
Loading
Loading
Loading
Loading
+58 −41
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.accounts.AccountManager;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.SdpPseRecord;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -184,6 +185,48 @@ public class PbapClientService extends ProfileService {
        setComponentAvailable(AUTHENTICATOR_SERVICE, false);
    }

    /**
     * Add our PBAP Client SDP record to the device SDP database
     *
     * <p>This allows our client to be recognized by the remove device. The record must be cleaned
     * up when we shutdown.
     */
    private void registerSdpRecord() {
        SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance();
        if (!nativeInterface.isAvailable()) {
            Log.e(TAG, "SdpManagerNativeInterface is not available");
            return;
        }
        mSdpHandle = nativeInterface.createPbapPceRecord(SERVICE_NAME, PbapSdpRecord.VERSION_1_2);
    }

    /**
     * Remove our PBAP Client SDP record from the device SDP database
     *
     * <p>Gracefully removes PBAP Client support from our SDP records. Called when shutting down.
     */
    private void cleanUpSdpRecord() {
        if (mSdpHandle < 0) {
            Log.e(TAG, "cleanUpSdpRecord, SDP record never created");
            return;
        }
        int sdpHandle = mSdpHandle;
        mSdpHandle = -1;
        SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance();
        if (!nativeInterface.isAvailable()) {
            Log.e(
                    TAG,
                    "cleanUpSdpRecord failed, SdpManagerNativeInterface is not available,"
                            + " sdpHandle="
                            + sdpHandle);
            return;
        }
        Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
        if (!nativeInterface.removeSdpRecord(sdpHandle)) {
            Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
        }
    }

    void cleanupDevice(BluetoothDevice device) {
        Log.d(TAG, "Cleanup device: " + device);
        synchronized (mPbapClientStateMachineMap) {
@@ -277,39 +320,6 @@ public class PbapClientService extends ProfileService {
        }
    }

    private void registerSdpRecord() {
        SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance();
        if (!nativeInterface.isAvailable()) {
            Log.e(TAG, "SdpManagerNativeInterface is not available");
            return;
        }
        mSdpHandle =
                nativeInterface.createPbapPceRecord(
                        SERVICE_NAME, PbapClientConnectionHandler.PBAP_V1_2);
    }

    private void cleanUpSdpRecord() {
        if (mSdpHandle < 0) {
            Log.e(TAG, "cleanUpSdpRecord, SDP record never created");
            return;
        }
        int sdpHandle = mSdpHandle;
        mSdpHandle = -1;
        SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance();
        if (!nativeInterface.isAvailable()) {
            Log.e(
                    TAG,
                    "cleanUpSdpRecord failed, SdpManagerNativeInterface is not available,"
                            + " sdpHandle="
                            + sdpHandle);
            return;
        }
        Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
        if (!nativeInterface.removeSdpRecord(sdpHandle)) {
            Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
        }
    }

    @VisibleForTesting
    class PbapBroadcastReceiver extends BroadcastReceiver {
        @Override
@@ -399,11 +409,6 @@ public class PbapClientService extends ProfileService {
     */
    public void receiveSdpSearchRecord(
            BluetoothDevice device, int status, Parcelable record, ParcelUuid uuid) {
        PbapClientStateMachine stateMachine = mPbapClientStateMachineMap.get(device);
        if (stateMachine == null) {
            Log.e(TAG, "No Statemachine found for the device=" + device.toString());
            return;
        }
        Log.v(
                TAG,
                "Received SDP record for UUID="
@@ -412,9 +417,21 @@ public class PbapClientService extends ProfileService {
                        + BluetoothUuid.PBAP_PSE.toString()
                        + ")");
        if (uuid.equals(BluetoothUuid.PBAP_PSE)) {
            PbapClientStateMachine stateMachine = mPbapClientStateMachineMap.get(device);
            if (stateMachine == null) {
                Log.e(TAG, "No Statemachine found for the device=" + device.toString());
                return;
            }
            SdpPseRecord pseRecord = (SdpPseRecord) record;
            if (pseRecord != null) {
                stateMachine
                    .obtainMessage(PbapClientStateMachine.MSG_SDP_COMPLETE, record)
                        .obtainMessage(
                                PbapClientStateMachine.MSG_SDP_COMPLETE,
                                new PbapSdpRecord(device, pseRecord))
                        .sendToTarget();
            } else {
                Log.w(TAG, "Received null PSE record for device=" + device);
            }
        }
    }

+186 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.bluetooth.BluetoothDevice;
import android.bluetooth.SdpPseRecord;

import java.util.Objects;

/**
 * This object represents an SDP Record for the PBAP profile. It extends the framework class by
 * housing all the supported feature masks.
 */
public class PbapSdpRecord {
    public static final int VERSION_1_0 = 0x0100;
    public static final int VERSION_1_1 = 0x0101;
    public static final int VERSION_1_2 = 0x0102;

    public static final int FEATURES_EXCLUDED = -1;
    public static final int FEATURE_DOWNLOADING = 1 << 0;
    public static final int FEATURE_BROWSING = 1 << 1;
    public static final int FEATURE_DATABASE_IDENTIFIER = 1 << 2;
    public static final int FEATURE_FOLDER_VERSION_COUNTERS = 1 << 3;
    public static final int FEATURE_VCARD_SELECTING = 1 << 4;
    public static final int FEATURE_ENHANCED_MISSED_CALLS = 1 << 5;
    public static final int FEATURE_XBT_UCI_VCARD_PROPERTY = 1 << 6;
    public static final int FEATURE_XBT_UID_VCARD_PROPERTY = 1 << 7;
    public static final int FEATURE_CONTACT_REFERENCING = 1 << 8;
    public static final int FEATURE_DEFAULT_IMAGE_FORMAT = 1 << 9;

    // PBAP v1.2.3 Sec. 7.1.2
    public static final int REPOSITORY_LOCAL_PHONEBOOK = 1 << 0;
    public static final int REPOSITORY_SIM_CARD = 1 << 1;
    public static final int REPOSITORY_SPEED_DIAL = 1 << 2;
    public static final int REPOSITORY_FAVORITES = 1 << 3;

    public static final int FIELD_MISSING = -1;

    private final BluetoothDevice mDevice;
    private final SdpPseRecord mSdpRecord;

    PbapSdpRecord(BluetoothDevice device, SdpPseRecord record) {
        mDevice = Objects.requireNonNull(device);
        mSdpRecord = Objects.requireNonNull(record);
    }

    /** Get the device associated with this SDP record */
    public BluetoothDevice getDevice() {
        return mDevice;
    }

    /** Get the profile version associated with this SDP record */
    public int getProfileVersion() {
        return mSdpRecord.getProfileVersion();
    }

    /** Get the service name associated with this SDP record */
    public String getServiceName() {
        return mSdpRecord.getServiceName();
    }

    /** Get the L2CAP PSM associated with this SDP record */
    public int getL2capPsm() {
        return mSdpRecord.getL2capPsm();
    }

    /** Get the RFCOMM channel number associated with this SDP record */
    public int getRfcommChannelNumber() {
        return mSdpRecord.getRfcommChannelNumber();
    }

    /** Get the supported features associated with this SDP record */
    public int getSupportedFeatures() {
        return mSdpRecord.getSupportedFeatures();
    }

    /** Returns true if this SDP record supports a given feature */
    public boolean isFeatureSupported(int feature) {
        int remoteFeatures = mSdpRecord.getSupportedFeatures();
        if (remoteFeatures != FIELD_MISSING) {
            return (feature & remoteFeatures) != 0;
        }
        return false;
    }

    /** Git the supported repositories bitmask associated with this SDP record */
    public int getSupportedRepositories() {
        return mSdpRecord.getSupportedRepositories();
    }

    /** Returns true if this SDP record supports a given repository */
    public boolean isRepositorySupported(int repository) {
        int remoteRepositories = mSdpRecord.getSupportedRepositories();
        if (remoteRepositories != FIELD_MISSING) {
            return (repository & remoteRepositories) != 0;
        }
        return false;
    }

    /** Get a string representation of this SDP record */
    @Override
    public String toString() {
        return mSdpRecord.toString();
    }

    /**
     * Get a string representation of any of the SDP PBAP version constants
     *
     * <p>Version is represented as a series of specification defined constants, in the form:
     * 0x[Major 2 bytes][Minor 2 bytes] -> [Major].[Minor]
     *
     * <p>For example, 0x0102 is 1.2.
     */
    public static String versionToString(int version) {
        switch (version) {
            case FIELD_MISSING:
                return "VERSION_UNKNOWN";
            case VERSION_1_0:
                return "VERSION_1_0";
            case VERSION_1_1:
                return "VERSION_1_1";
            case VERSION_1_2:
                return "VERSION_1_2";
            default:
                return "VERSION_UNRECOGNIZED_" + String.format("%04X", version);
        }
    }

    /** Get a string representation of any of the SDP feature constants */
    public static String featureToString(int feature) {
        switch (feature) {
            case FEATURE_DOWNLOADING:
                return "FEATURE_DOWNLOADING";
            case FEATURE_BROWSING:
                return "FEATURE_BROWSING";
            case FEATURE_DATABASE_IDENTIFIER:
                return "FEATURE_DATABASE_IDENTIFIER";
            case FEATURE_FOLDER_VERSION_COUNTERS:
                return "FEATURE_FOLDER_VERSION_COUNTERS";
            case FEATURE_VCARD_SELECTING:
                return "FEATURE_VCARD_SELECTING";
            case FEATURE_ENHANCED_MISSED_CALLS:
                return "FEATURE_ENHANCED_MISSED_CALLS";
            case FEATURE_XBT_UCI_VCARD_PROPERTY:
                return "FEATURE_XBT_UCI_VCARD_PROPERTY";
            case FEATURE_XBT_UID_VCARD_PROPERTY:
                return "FEATURE_XBT_UID_VCARD_PROPERTY";
            case FEATURE_CONTACT_REFERENCING:
                return "FEATURE_CONTACT_REFERENCING";
            case FEATURE_DEFAULT_IMAGE_FORMAT:
                return "FEATURE_DEFAULT_IMAGE_FORMAT";
            default:
                return "FEATURE_RESERVED_BIT_" + feature;
        }
    }

    /** Get a string representation of any of the SDP repository constants */
    public static String repositoryToString(int repository) {
        switch (repository) {
            case REPOSITORY_LOCAL_PHONEBOOK:
                return "REPOSITORY_LOCAL_PHONEBOOK";
            case REPOSITORY_SIM_CARD:
                return "REPOSITORY_SIM_CARD";
            case REPOSITORY_SPEED_DIAL:
                return "REPOSITORY_SPEED_DIAL";
            case REPOSITORY_FAVORITES:
                return "REPOSITORY_FAVORITES";
            default:
                return "REPOSITORY_RESERVED_BIT_" + repository;
        }
    }
}
+18 −33
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.SdpPseRecord;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
@@ -61,6 +60,9 @@ class PbapClientConnectionHandler extends Handler {
    static final int MSG_DISCONNECT = 2;
    static final int MSG_DOWNLOAD = 3;

    static final int L2CAP_INVALID_PSM = -1;
    static final int RFCOMM_INVALID_CHANNEL_ID = -1;

    // The following constants are pulled from the Bluetooth Phone Book Access Profile specification
    // 1.1
    private static final byte[] PBAP_TARGET =
@@ -83,27 +85,15 @@ class PbapClientConnectionHandler extends Handler {
                0x66
            };

    private static final int PBAP_FEATURE_DEFAULT_IMAGE_FORMAT = 0x00000200;
    private static final int PBAP_FEATURE_DOWNLOADING = 0x00000001;

    private static final int PBAP_SUPPORTED_FEATURE =
            PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_DOWNLOADING;

    @VisibleForTesting static final int L2CAP_INVALID_PSM = -1;

    // PBAP v1.2.3 Sec. 7.1.2
    private static final int SUPPORTED_REPOSITORIES_LOCALPHONEBOOK = 1 << 0;
    private static final int SUPPORTED_REPOSITORIES_SIMCARD = 1 << 1;
    private static final int SUPPORTED_REPOSITORIES_FAVORITES = 1 << 3;

    public static final int PBAP_V1_2 = 0x0102;
    private static final int PBAP_SUPPORTED_FEATURES =
            PbapSdpRecord.FEATURE_DEFAULT_IMAGE_FORMAT | PbapSdpRecord.FEATURE_DOWNLOADING;

    private Account mAccount;
    private AccountManager mAccountManager;
    private BluetoothSocket mSocket;
    private final BluetoothDevice mDevice;
    // PSE SDP Record for current device.
    private SdpPseRecord mPseRec = null;
    private PbapSdpRecord mPseRec = null;
    private ClientSession mObexSession;
    private Context mContext;
    private PbapClientObexAuthenticator mAuth = null;
@@ -166,7 +156,8 @@ class PbapClientConnectionHandler extends Handler {
        Log.d(TAG, "Handling Message = " + msg.what);
        switch (msg.what) {
            case MSG_CONNECT:
                mPseRec = (SdpPseRecord) msg.obj;
                mPseRec = (PbapSdpRecord) msg.obj;

                /* To establish a connection, first open a socket and then create an OBEX session */
                if (connectSocket()) {
                    Log.d(TAG, "Socket connected");
@@ -215,13 +206,13 @@ class PbapClientConnectionHandler extends Handler {
                    Log.e(TAG, "Account creation failed.");
                    return;
                }
                if (isRepositorySupported(SUPPORTED_REPOSITORIES_FAVORITES)) {
                if (mPseRec.isRepositorySupported(PbapSdpRecord.REPOSITORY_FAVORITES)) {
                    downloadContacts(PbapPhonebook.FAVORITES_PATH);
                }
                if (isRepositorySupported(SUPPORTED_REPOSITORIES_LOCALPHONEBOOK)) {
                if (mPseRec.isRepositorySupported(PbapSdpRecord.REPOSITORY_LOCAL_PHONEBOOK)) {
                    downloadContacts(PbapPhonebook.LOCAL_PHONEBOOK_PATH);
                }
                if (isRepositorySupported(SUPPORTED_REPOSITORIES_SIMCARD)) {
                if (mPseRec.isRepositorySupported(PbapSdpRecord.REPOSITORY_SIM_CARD)) {
                    downloadContacts(PbapPhonebook.SIM_PHONEBOOK_PATH);
                }

@@ -237,7 +228,7 @@ class PbapClientConnectionHandler extends Handler {
    }

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

@@ -261,9 +252,12 @@ class PbapClientConnectionHandler extends Handler {
            } else if (mPseRec.getL2capPsm() != L2CAP_INVALID_PSM) {
                Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm());
                mSocket = mDevice.createL2capSocket(mPseRec.getL2capPsm());
            } else {
            } else if (mPseRec.getRfcommChannelNumber() != RFCOMM_INVALID_CHANNEL_ID) {
                Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber());
                mSocket = mDevice.createRfcommSocket(mPseRec.getRfcommChannelNumber());
            } else {
                Log.w(TAG, "connectSocket: transport PSM or channel ID not specified");
                return false;
            }

            if (mSocket != null) {
@@ -298,10 +292,10 @@ class PbapClientConnectionHandler extends Handler {

                ObexAppParameters oap = new ObexAppParameters();

                if (mPseRec.getProfileVersion() >= PBAP_V1_2) {
                if (mPseRec.getProfileVersion() >= PbapSdpRecord.VERSION_1_2) {
                    oap.add(
                            PbapApplicationParameters.OAP_PBAP_SUPPORTED_FEATURES,
                            PBAP_SUPPORTED_FEATURE);
                            PBAP_SUPPORTED_FEATURES);
                }

                oap.addToHeaderSet(connectionRequest);
@@ -469,13 +463,4 @@ class PbapClientConnectionHandler extends Handler {
            Log.d(TAG, "Call Logs could not be deleted, they may not exist yet.");
        }
    }

    @VisibleForTesting
    boolean isRepositorySupported(int mask) {
        if (mPseRec == null) {
            Log.v(TAG, "No PBAP Server SDP Record");
            return false;
        }
        return (mask & mPseRec.getSupportedRepositories()) != 0;
    }
}
+3 −22
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.SdpPseRecord;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
@@ -126,7 +125,7 @@ public class PbapClientConnectionHandlerTest {

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

        when(record.getL2capPsm()).thenReturn(PbapClientConnectionHandler.L2CAP_INVALID_PSM);
@@ -135,7 +134,7 @@ public class PbapClientConnectionHandlerTest {

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

        when(record.getL2capPsm()).thenReturn(1); // Valid PSM ranges 1 to 30;
@@ -151,7 +150,7 @@ public class PbapClientConnectionHandlerTest {

    @Test
    public void abort() {
        SdpPseRecord record = mock(SdpPseRecord.class);
        PbapSdpRecord record = mock(PbapSdpRecord.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
@@ -174,24 +173,6 @@ public class PbapClientConnectionHandlerTest {
        mHandler.removeCallLog();
    }

    @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();
    }

    @Test
    public void createAndDisconnectWithoutAddingAccount_doesNotCrash() {
        mHandler.obtainMessage(PbapClientConnectionHandler.MSG_DISCONNECT).sendToTarget();
+459 −0

File added.

Preview size limit exceeded, changes collapsed.