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

Commit a93f49fb authored by Joseph Pirozzo's avatar Joseph Pirozzo Committed by android-build-merger
Browse files

PBAP over L2CAP

am: b2c3dc95

Change-Id: I1731e397c57289b0894d54f69c54765ce30782c5
parents 926cb6d1 b2c3dc95
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