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

Commit 0d6e1346 authored by Joseph Pirozzo's avatar Joseph Pirozzo Committed by Sanket Agarwal
Browse files

PBAP over L2CAP

Update PBAP to be compliant with socket connections over L2CAP in
addition to RFCOMM.  Additionally now requires SDP to complete before
connection.

Bug: 31062208
Change-Id: Idc991d871e9a67c7d6b631cafbfb98ff45d63c38
(cherry picked from commit ea2543a5009459fee3581d2fd8b2271511d31a4c)
parent 990e8f6d
Loading
Loading
Loading
Loading
+10 −27
Original line number Diff line number Diff line
@@ -22,13 +22,18 @@ import android.util.Log;
import javax.obex.Authenticator;
import javax.obex.PasswordAuthentication;

/* ObexAuthentication is a required component for PBAP in order to support backwards compatibility
 * with PSE devices prior to PBAP 1.2. With profiles prior to 1.2 the actual initiation of
 * authentication is implementation defined.
 */


class BluetoothPbapObexAuthenticator implements Authenticator {

    private final static String TAG = "BluetoothPbapObexAuthenticator";

    private String mSessionKey;

    private boolean mReplied;
    //Default session key for legacy devices is 0000
    private String mSessionKey = "0000";

    private final Handler mCallback;

@@ -36,34 +41,11 @@ class BluetoothPbapObexAuthenticator implements Authenticator {
        mCallback = callback;
    }

    public synchronized void setReply(String key) {
        Log.d(TAG, "setReply key=" + key);

        mSessionKey = key;
        mReplied = true;

        notify();
    }

    @Override
    public PasswordAuthentication onAuthenticationChallenge(String description,
            boolean isUserIdRequired, boolean isFullAccess) {
        PasswordAuthentication pa = null;

        mReplied = false;

        Log.d(TAG, "onAuthenticationChallenge: sending request");

        synchronized (this) {
            while (!mReplied) {
                try {
                    Log.v(TAG, "onAuthenticationChallenge: waiting for response");
                    this.wait();
                } catch (InterruptedException e) {
                    Log.e(TAG, "Interrupted while waiting for challenge response");
                }
            }
        }
        Log.v(TAG, "onAuthenticationChallenge: starting");

        if (mSessionKey != null && mSessionKey.length() != 0) {
            Log.v(TAG, "onAuthenticationChallenge: mSessionKey=" + mSessionKey);
@@ -77,6 +59,7 @@ class BluetoothPbapObexAuthenticator implements Authenticator {

    @Override
    public byte[] onAuthenticationResponse(byte[] userName) {
        Log.v(TAG, "onAuthenticationResponse: " + userName);
        /* required only in case PCE challenges PSE which we don't do now */
        return null;
    }
+1 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ abstract class BluetoothPbapRequest {
    protected static final byte OAP_TAGID_FORMAT = 0x07;
    protected static final byte OAP_TAGID_PHONEBOOK_SIZE = 0x08;
    protected static final byte OAP_TAGID_NEW_MISSED_CALLS = 0x09;
    protected static final byte OAP_TAGID_PBAP_SUPPORTED_FEATURES = 0x10;

    protected HeaderSet mHeaderSet;

+1 −1
Original line number Diff line number Diff line
@@ -20,7 +20,7 @@ import android.accounts.Account;
import android.util.Log;

import com.android.vcard.VCardEntry;
import com.android.bluetooth.pbapclient.utils.ObexAppParameters;
import com.android.bluetooth.pbapclient.ObexAppParameters;

import java.io.IOException;
import java.io.InputStream;
+1 −1
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

package com.android.bluetooth.pbapclient.utils;
package com.android.bluetooth.pbapclient;

import java.io.IOException;
import java.nio.ByteBuffer;
+164 −43
Original line number Diff line number Diff line
@@ -21,15 +21,15 @@ import android.bluetooth.BluetoothAdapter;
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.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.provider.CallLog;
import android.util.Log;

import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.R;

import java.io.IOException;
@@ -57,6 +57,15 @@ class PbapClientConnectionHandler extends Handler {
            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
    };

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

    private static final int PBAP_SUPPORTED_FEATURE =
            PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_BROWSING | PBAP_FEATURE_DOWNLOADING;
    private static final int PBAP_V1_2 = 0x0102;
    private static final int L2CAP_INVALID_PSM = -1;

    public static final String PB_PATH = "telecom/pb.vcf";
    public static final String MCH_PATH = "telecom/mch.vcf";
    public static final String ICH_PATH = "telecom/ich.vcf";
@@ -69,6 +78,8 @@ class PbapClientConnectionHandler extends Handler {
    private BluetoothSocket mSocket;
    private final BluetoothAdapter mAdapter;
    private final BluetoothDevice mDevice;
    // PSE SDP Record for current device.
    private SdpPseRecord mPseRec = null;
    private ClientSession mObexSession;
    private Context mContext;
    private BluetoothPbapObexAuthenticator mAuth = null;
@@ -88,41 +99,74 @@ class PbapClientConnectionHandler extends Handler {
                R.string.pbap_account_type));
    }

    /**
     * Constructs PCEConnectionHandler object
     *
     * @param Builder To build  BluetoothPbapClientHandler Instance.
     */
    PbapClientConnectionHandler(Builder pceHandlerbuild) {
        super(pceHandlerbuild.looper);
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mDevice = pceHandlerbuild.device;
        mContext = pceHandlerbuild.context;
        mPbapClientStateMachine = pceHandlerbuild.clientStateMachine;
        mAuth = new BluetoothPbapObexAuthenticator(this);
        mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext());
        mAccount = new Account(mDevice.getAddress(), mContext.getString(
                R.string.pbap_account_type));
    }

    public static class Builder {

        private Looper looper;
        private Context context;
        private BluetoothDevice device;
        private PbapClientStateMachine clientStateMachine;

        public Builder setLooper(Looper loop) {
            this.looper = loop;
            return this;
        }

        public Builder setClientSM(PbapClientStateMachine clientStateMachine) {
            this.clientStateMachine = clientStateMachine;
            return this;
        }

        public Builder setRemoteDevice(BluetoothDevice device) {
            this.device = device;
            return this;
        }

        public Builder setContext(Context context) {
            this.context = context;
            return this;
        }

        public PbapClientConnectionHandler build() {
            PbapClientConnectionHandler pbapClientHandler = new PbapClientConnectionHandler(this);
            return pbapClientHandler;
        }

    }

    @Override
    public void handleMessage(Message msg) {
        if (DBG) Log.d(TAG, "Handling Message = " + msg.what);
        switch (msg.what) {
            case MSG_CONNECT:
                boolean connectionSuccessful = false;

                try {
                    /* To establish a connection first open a socket, establish a OBEX Transport
                     * abstraction, establish a Bluetooth Authenticator, and finally attempt to
                     * connect via an OBEX session */
                    mSocket = mDevice.createRfcommSocketToServiceRecord(
                            BluetoothUuid.PBAP_PSE.getUuid());
                    if (DBG) Log.d(TAG, "Socket created.");
                    mSocket.connect();
                    if (DBG) Log.d(TAG, "Socket connected.");
                    BluetoothPbapObexTransport transport;
                    transport = new BluetoothPbapObexTransport(mSocket);

                    mObexSession  = new ClientSession(transport);
                    mObexSession.setAuthenticator(mAuth);

                    HeaderSet connectionRequest = new HeaderSet();
                    connectionRequest.setHeader(HeaderSet.TARGET, PBAP_TARGET);
                    HeaderSet connectionResponse = mObexSession.connect(connectionRequest);

                    connectionSuccessful = (connectionResponse.getResponseCode() ==
                            ResponseCodes.OBEX_HTTP_OK);
                    if (DBG) Log.d(TAG,"Success = " + Boolean.toString(connectionSuccessful));
                } catch (IOException e) {
                    Log.w(TAG,"CONNECT Failure " + e.toString());
                    closeSocket();
                mPseRec = (SdpPseRecord) msg.obj;
                /* To establish a connection, first open a socket and then create an OBEX session */
                if (connectSocket()) {
                    if (DBG) Log.d(TAG, "Socket connected");
                } else {
                    Log.w(TAG, "Socket CONNECT Failure ");
                    mPbapClientStateMachine.obtainMessage(
                            PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
                    return;
                }

                if (connectionSuccessful) {
                if (connectObexSession()) {
                    mPbapClientStateMachine.obtainMessage(
                            PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget();
                } else {
@@ -135,14 +179,17 @@ class PbapClientConnectionHandler extends Handler {
                if (DBG) Log.d(TAG, "Starting Disconnect");
                try {
                    if (mObexSession != null) {
                        if (DBG) Log.d(TAG, "obexSessionDisconnect" + mObexSession);
                        mObexSession.disconnect(null);
                        mObexSession.close();
                    }

                    if (DBG) Log.d(TAG, "Closing Socket");
                    closeSocket();
                } catch (IOException e) {
                    Log.w(TAG, "DISCONNECT Failure ", e);
                }
                Log.d(TAG, "Completing Disconnect");
                if (DBG) Log.d(TAG, "Completing Disconnect");
                removeAccount(mAccount);
                mContext.getContentResolver()
                        .delete(CallLog.Calls.CONTENT_URI, null, null);
@@ -160,10 +207,11 @@ class PbapClientConnectionHandler extends Handler {
                    // Start at contact 1 to exclued Owner Card PBAP 1.1 sec 3.1.5.2
                    BluetoothPbapRequestPullPhoneBook request =
                            new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount, 0,
                            VCARD_TYPE_21, 0, 1);
                                    VCARD_TYPE_30, 0, 1);
                    request.execute(mObexSession);
                    PhonebookPullRequest processor =
                        new PhonebookPullRequest(mPbapClientStateMachine.getContext(), mAccount);
                            new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
                                    mAccount);
                    processor.setResults(request.getList());
                    processor.onPullComplete();

@@ -181,6 +229,77 @@ class PbapClientConnectionHandler extends Handler {
        return;
    }

    /* Utilize SDP, if available, to create a socket connection over L2CAP, RFCOMM specified
     * channel, or RFCOMM default channel. */
    private boolean connectSocket() {
        try {
            /* Use BluetoothSocket to connect */
            if (mPseRec == null) {
                // BackWardCompatability: Fall back to create RFCOMM through UUID.
                Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid());
                mSocket = mDevice.createRfcommSocketToServiceRecord(
                        BluetoothUuid.PBAP_PSE.getUuid());
            } else if (mPseRec.getL2capPsm() != L2CAP_INVALID_PSM) {
                Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm());
                mSocket = mDevice.createL2capSocket(mPseRec.getL2capPsm());
            } else {
                Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber());
                mSocket = mDevice.createRfcommSocket(mPseRec.getRfcommChannelNumber());
            }

            if (mSocket != null) {
                mSocket.connect();
                return true;
            } else {
                Log.w(TAG, "Could not create socket");
            }
        } catch (IOException e) {
            Log.e(TAG, "Error while connecting socket", e);
        }
        return false;
    }

    /* 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() {
        boolean connectionSuccessful = false;

        try {
            if (DBG) Log.v(TAG, "Start Obex Client Session");
            BluetoothObexTransport transport = new BluetoothObexTransport(mSocket);
            mObexSession = new ClientSession(transport);
            mObexSession.setAuthenticator(mAuth);

            HeaderSet connectionRequest = new HeaderSet();
            connectionRequest.setHeader(HeaderSet.TARGET, PBAP_TARGET);

            if (mPseRec != null) {
                if (DBG) {
                    Log.d(TAG, "Remote PbapSupportedFeatures "
                            + mPseRec.getSupportedFeatures());
                }

                ObexAppParameters oap = new ObexAppParameters();

                if (mPseRec.getProfileVersion() >= PBAP_V1_2) {
                    oap.add(BluetoothPbapRequest.OAP_TAGID_PBAP_SUPPORTED_FEATURES,
                            PBAP_SUPPORTED_FEATURE);
                }

                oap.addToHeaderSet(connectionRequest);
            }
            HeaderSet connectionResponse = mObexSession.connect(connectionRequest);

            connectionSuccessful = (connectionResponse.getResponseCode() ==
                    ResponseCodes.OBEX_HTTP_OK);
            if (DBG) Log.d(TAG, "Success = " + Boolean.toString(connectionSuccessful));
        } catch (IOException e) {
            Log.w(TAG, "CONNECT Failure " + e.toString());
            closeSocket();
        }
        return connectionSuccessful;
    }

    public 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.
@@ -191,6 +310,7 @@ class PbapClientConnectionHandler extends Handler {
    private void closeSocket() {
        try {
            if (mSocket != null) {
                if (DBG) Log.d(TAG, "Closing socket" + mSocket);
                mSocket.close();
                mSocket = null;
            }
@@ -199,10 +319,11 @@ class PbapClientConnectionHandler extends Handler {
            mSocket = null;
        }
    }

    void downloadCallLog(String path) {
        try {
            BluetoothPbapRequestPullPhoneBook request =
                    new BluetoothPbapRequestPullPhoneBook(path,mAccount,0,VCARD_TYPE_21,0,0);
                    new BluetoothPbapRequestPullPhoneBook(path, mAccount, 0, VCARD_TYPE_30, 0, 0);
            request.execute(mObexSession);
            CallLogPullRequest processor =
                    new CallLogPullRequest(mPbapClientStateMachine.getContext(), path);
Loading