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

Commit a91c15d0 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge changes I09bb514a,Ia3a4905b into main

* changes:
  Create PbapSdpRecord wrapper for record and constants
  Clean up base request object and subclasses
parents a3fdde14 9fe5c133
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;
    }
}
+56 −50
Original line number Diff line number Diff line
@@ -29,91 +29,97 @@ import java.io.InputStream;
abstract class PbapClientRequest {
    static final String TAG = PbapClientRequest.class.getSimpleName();

    protected HeaderSet mHeaderSet;
    // Request Types
    public static final int TYPE_PULL_PHONEBOOK_METADATA = 0;
    public static final int TYPE_PULL_PHONEBOOK = 1;

    protected int mResponseCode;

    private boolean mAborted = false;

    private ClientOperation mOp = null;
    protected HeaderSet mHeaderSet = new HeaderSet();
    private int mResponseCode = -1;

    PbapClientRequest() {
        mHeaderSet = new HeaderSet();
        mResponseCode = -1;
    }

    public final boolean isSuccess() {
        return (mResponseCode == ResponseCodes.OBEX_HTTP_OK);
    /**
     * A function that returns the type of the request.
     *
     * <p>Used to determine type instead of using 'instanceof'
     */
    public abstract int getType();

    /**
     * Get the actual response code associated with the request
     *
     * @return The response code as in integer
     */
    public final int getResponseCode() {
        return mResponseCode;
    }

    /**
     * A generica operation, providing overridable hooks to read response headers and content.
     *
     * <p>All PBAP Client operations are GET OBEX operations, so that is what this is.
     */
    public void execute(ClientSession session) throws IOException {
        Log.v(TAG, "execute");

        /* in case request is aborted before can be executed */
        if (mAborted) {
            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
            return;
        }

        ClientOperation operation = null;
        try {
            mOp = (ClientOperation) session.get(mHeaderSet);
            operation = (ClientOperation) session.get(mHeaderSet);

            /* make sure final flag for GET is used (PBAP spec 6.2.2) */
            mOp.setGetFinalFlag(true);
            operation.setGetFinalFlag(true);

            /*
             * this will trigger ClientOperation to use non-buffered stream so
             * we can abort operation
             */
            mOp.continueOperation(true, false);

            readResponseHeaders(mOp.getReceivedHeader());

            InputStream is = mOp.openInputStream();
            readResponse(is);
            is.close();

            mOp.close();
            operation.continueOperation(true, false);

            mResponseCode = mOp.getResponseCode();

            Log.d(TAG, "mResponseCode=" + mResponseCode);

            checkResponseCode(mResponseCode);
            readResponseHeaders(operation.getReceivedHeader());
            InputStream inputStream = operation.openInputStream();
            readResponse(inputStream);
            inputStream.close();
            mResponseCode = operation.getResponseCode();
        } catch (IOException e) {
            Log.e(TAG, "IOException occurred when processing request", e);
            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;

            Log.e(TAG, "IOException occurred when processing request", e);
            throw e;
        }
    }

    public void abort() {
        mAborted = true;

        if (mOp != null) {
            try {
                mOp.abort();
            } catch (IOException e) {
                Log.e(TAG, "Exception occurred when trying to abort", e);
        } finally {
            // Always close the operation so the next operation can successfully complete
            if (operation != null) {
                operation.close();
            }
        }
    }

    protected void readResponse(InputStream stream) throws IOException {
        Log.v(TAG, "readResponse");

        /* nothing here by default */
    }

    protected void readResponseHeaders(HeaderSet headerset) {
        Log.v(TAG, "readResponseHeaders");

        /* nothing here by default */
    }

    protected void checkResponseCode(int responseCode) throws IOException {
        Log.v(TAG, "checkResponseCode");
    public static String typeToString(int type) {
        switch (type) {
            case TYPE_PULL_PHONEBOOK_METADATA:
                return "TYPE_PULL_PHONEBOOK_METADATA";
            case TYPE_PULL_PHONEBOOK:
                return "TYPE_PULL_PHONEBOOK";
            default:
                return "TYPE_RESERVED (" + type + ")";
        }
    }

        /* nothing here by default */
    @Override
    public String toString() {
        return "<"
                + TAG
                + (" type=" + typeToString(getType()))
                + (", responseCode=" + getResponseCode())
                + ">";
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.bluetooth.pbapclient;

import android.accounts.Account;
import android.util.Log;

import com.android.bluetooth.ObexAppParameters;
import com.android.obex.HeaderSet;
@@ -40,6 +39,11 @@ final class RequestPullPhonebook extends PbapClientRequest {

    private PbapPhonebook mResponse;

    @Override
    public int getType() {
        return TYPE_PULL_PHONEBOOK;
    }

    RequestPullPhonebook(String phonebook, PbapApplicationParameters params, Account account) {
        mPhonebook = phonebook;
        mFormat = params.getVcardFormat();
@@ -80,10 +84,7 @@ final class RequestPullPhonebook extends PbapClientRequest {

    @Override
    protected void readResponse(InputStream stream) throws IOException {
        Log.v(TAG, "readResponse");

        mResponse = new PbapPhonebook(mPhonebook, mFormat, mListStartOffset, mAccount, stream);
        Log.d(TAG, "Read " + mResponse.getCount() + " entries");
    }

    public String getPhonebook() {
Loading