PBAP: Support PBAP Client role on Bluedroid.

Implementation of org.codeaurora.bluetooth.pbapclient,
STATIC JAVA lib for PBAP client role.

Change-Id: I76919204c5cc342325da37250d5d2ae00f05f39f
CRs-fixed: 530274
diff --git a/Android.mk b/Android.mk
index 33c1115..4e4a62c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -37,4 +37,20 @@
 LOCAL_PROGUARD_ENABLED := disabled
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+
+src_dirs:=  src/org/codeaurora/bluetooth/pbapclient
+
+LOCAL_SRC_FILES := \
+        $(call all-java-files-under, $(src_dirs))
+
+LOCAL_MODULE:= org.codeaurora.bluetooth.pbapclient
+LOCAL_JAVA_LIBRARIES := javax.obex
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapCard.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapCard.java
new file mode 100644
index 0000000..21d9371
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapCard.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntry.EmailData;
+import com.android.vcard.VCardEntry.NameData;
+import com.android.vcard.VCardEntry.PhoneData;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * Entry representation of folder listing
+ */
+public class BluetoothPbapCard {
+
+    public final String handle;
+
+    public final String N;
+    public final String lastName;
+    public final String firstName;
+    public final String middleName;
+    public final String prefix;
+    public final String suffix;
+
+    public BluetoothPbapCard(String handle, String name) {
+        this.handle = handle;
+
+        N = name;
+
+        /*
+         * format is as for vCard N field, so we have up to 5 tokens: LastName;
+         * FirstName; MiddleName; Prefix; Suffix
+         */
+        String[] parsedName = name.split(";", 5);
+
+        lastName = parsedName.length < 1 ? null : parsedName[0];
+        firstName = parsedName.length < 2 ? null : parsedName[1];
+        middleName = parsedName.length < 3 ? null : parsedName[2];
+        prefix = parsedName.length < 4 ? null : parsedName[3];
+        suffix = parsedName.length < 5 ? null : parsedName[4];
+    }
+
+    @Override
+    public String toString() {
+        JSONObject json = new JSONObject();
+
+        try {
+            json.put("handle", handle);
+            json.put("N", N);
+            json.put("lastName", lastName);
+            json.put("firstName", firstName);
+            json.put("middleName", middleName);
+            json.put("prefix", prefix);
+            json.put("suffix", suffix);
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        return json.toString();
+    }
+
+    static public String jsonifyVcardEntry(VCardEntry vcard) {
+        JSONObject json = new JSONObject();
+
+        try {
+            NameData name = vcard.getNameData();
+            json.put("formatted", name.getFormatted());
+            json.put("family", name.getFamily());
+            json.put("given", name.getGiven());
+            json.put("middle", name.getMiddle());
+            json.put("prefix", name.getPrefix());
+            json.put("suffix", name.getSuffix());
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        try {
+            JSONArray jsonPhones = new JSONArray();
+
+            List<PhoneData> phones = vcard.getPhoneList();
+
+            if (phones != null) {
+                for (PhoneData phone : phones) {
+                    JSONObject jsonPhone = new JSONObject();
+                    jsonPhone.put("type", phone.getType());
+                    jsonPhone.put("number", phone.getNumber());
+                    jsonPhone.put("label", phone.getLabel());
+                    jsonPhone.put("is_primary", phone.isPrimary());
+
+                    jsonPhones.put(jsonPhone);
+                }
+
+                json.put("phones", jsonPhones);
+            }
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        try {
+            JSONArray jsonEmails = new JSONArray();
+
+            List<EmailData> emails = vcard.getEmailList();
+
+            if (emails != null) {
+                for (EmailData email : emails) {
+                    JSONObject jsonEmail = new JSONObject();
+                    jsonEmail.put("type", email.getType());
+                    jsonEmail.put("address", email.getAddress());
+                    jsonEmail.put("label", email.getLabel());
+                    jsonEmail.put("is_primary", email.isPrimary());
+
+                    jsonEmails.put(jsonEmail);
+                }
+
+                json.put("emails", jsonEmails);
+            }
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        return json.toString();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapClient.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapClient.java
new file mode 100644
index 0000000..b6e688b
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapClient.java
@@ -0,0 +1,858 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Public API to control Phone Book Profile (PCE role only).
+ * <p>
+ * This class defines methods that shall be used by application for the
+ * retrieval of phone book objects from remote device.
+ * <p>
+ * How to connect to remote device which is acting in PSE role:
+ * <ul>
+ * <li>Create a <code>BluetoothDevice</code> object which corresponds to remote
+ * device in PSE role;
+ * <li>Create an instance of <code>BluetoothPbapClient</code> class, passing
+ * <code>BluetothDevice</code> object along with a <code>Handler</code> to it;
+ * <li>Use {@link #setPhoneBookFolderRoot}, {@link #setPhoneBookFolderUp} and
+ * {@link #setPhoneBookFolderDown} to navigate in virtual phone book folder
+ * structure
+ * <li>Use {@link #pullPhoneBookSize} or {@link #pullVcardListingSize} to
+ * retrieve the size of selected phone book
+ * <li>Use {@link #pullPhoneBook} to retrieve phone book entries
+ * <li>Use {@link #pullVcardListing} to retrieve list of entries in the phone
+ * book
+ * <li>Use {@link #pullVcardEntry} to pull single entry from the phone book
+ * </ul>
+ * Upon completion of each call above PCE will notify application if operation
+ * completed successfully (along with results) or failed.
+ * <p>
+ * Therefore, application should handle following events in its message queue
+ * handler:
+ * <ul>
+ * <li><code>EVENT_PULL_PHONE_BOOK_SIZE_DONE</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_SIZE_DONE</code>
+ * <li><code>EVENT_PULL_PHONE_BOOK_DONE</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_DONE</code>
+ * <li><code>EVENT_PULL_VCARD_ENTRY_DONE</code>
+ * <li><code>EVENT_SET_PHONE_BOOK_DONE</code>
+ * </ul>
+ * and
+ * <ul>
+ * <li><code>EVENT_PULL_PHONE_BOOK_SIZE_ERROR</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_SIZE_ERROR</code>
+ * <li><code>EVENT_PULL_PHONE_BOOK_ERROR</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_ERROR</code>
+ * <li><code>EVENT_PULL_VCARD_ENTRY_ERROR</code>
+ * <li><code>EVENT_SET_PHONE_BOOK_ERROR</code>
+ * </ul>
+ * <code>connect</code> and <code>disconnect</code> methods are introduced for
+ * testing purposes. An application does not need to use them as the session
+ * connection and disconnection happens automatically internally.
+ */
+public class BluetoothPbapClient {
+    private static final String TAG = "BluetoothPbapClient";
+
+    /**
+     * Path to local incoming calls history object
+     */
+    public static final String ICH_PATH = "telecom/ich.vcf";
+
+    /**
+     * Path to local outgoing calls history object
+     */
+    public static final String OCH_PATH = "telecom/och.vcf";
+
+    /**
+     * Path to local missed calls history object
+     */
+    public static final String MCH_PATH = "telecom/mch.vcf";
+
+    /**
+     * Path to local combined calls history object
+     */
+    public static final String CCH_PATH = "telecom/cch.vcf";
+
+    /**
+     * Path to local main phone book object
+     */
+    public static final String PB_PATH = "telecom/pb.vcf";
+
+    /**
+     * Path to incoming calls history object stored on the phone's SIM card
+     */
+    public static final String SIM_ICH_PATH = "SIM1/telecom/ich.vcf";
+
+    /**
+     * Path to outgoing calls history object stored on the phone's SIM card
+     */
+    public static final String SIM_OCH_PATH = "SIM1/telecom/och.vcf";
+
+    /**
+     * Path to missed calls history object stored on the phone's SIM card
+     */
+    public static final String SIM_MCH_PATH = "SIM1/telecom/mch.vcf";
+
+    /**
+     * Path to combined calls history object stored on the phone's SIM card
+     */
+    public static final String SIM_CCH_PATH = "SIM1/telecom/cch.vcf";
+
+    /**
+     * Path to main phone book object stored on the phone's SIM card
+     */
+    public static final String SIM_PB_PATH = "SIM1/telecom/pb.vcf";
+
+    /**
+     * Indicates to server that default sorting order shall be used for vCard
+     * listing.
+     */
+    public static final byte ORDER_BY_DEFAULT = -1;
+
+    /**
+     * Indicates to server that indexed sorting order shall be used for vCard
+     * listing.
+     */
+    public static final byte ORDER_BY_INDEXED = 0;
+
+    /**
+     * Indicates to server that alphabetical sorting order shall be used for the
+     * vCard listing.
+     */
+    public static final byte ORDER_BY_ALPHABETICAL = 1;
+
+    /**
+     * Indicates to server that phonetical (based on sound attribute) sorting
+     * order shall be used for the vCard listing.
+     */
+    public static final byte ORDER_BY_PHONETIC = 2;
+
+    /**
+     * Indicates to server that Name attribute of vCard shall be used to carry
+     * out the search operation on
+     */
+    public static final byte SEARCH_ATTR_NAME = 0;
+
+    /**
+     * Indicates to server that Number attribute of vCard shall be used to carry
+     * out the search operation on
+     */
+    public static final byte SEARCH_ATTR_NUMBER = 1;
+
+    /**
+     * Indicates to server that Sound attribute of vCard shall be used to carry
+     * out the search operation
+     */
+    public static final byte SEARCH_ATTR_SOUND = 2;
+
+    /**
+     * VCard format version 2.1
+     */
+    public static final byte VCARD_TYPE_21 = 0;
+
+    /**
+     * VCard format version 3.0
+     */
+    public static final byte VCARD_TYPE_30 = 1;
+
+    /* 64-bit mask used to filter out VCard fields */
+    // TODO: Think of extracting to separate class
+    public static final long VCARD_ATTR_VERSION = 0x000000000000000001;
+    public static final long VCARD_ATTR_FN = 0x000000000000000002;
+    public static final long VCARD_ATTR_N = 0x000000000000000004;
+    public static final long VCARD_ATTR_PHOTO = 0x000000000000000008;
+    public static final long VCARD_ATTR_BDAY = 0x000000000000000010;
+    public static final long VCARD_ATTR_ADDR = 0x000000000000000020;
+    public static final long VCARD_ATTR_LABEL = 0x000000000000000040;
+    public static final long VCARD_ATTR_TEL = 0x000000000000000080;
+    public static final long VCARD_ATTR_EMAIL = 0x000000000000000100;
+    public static final long VCARD_ATTR_MAILER = 0x000000000000000200;
+    public static final long VCARD_ATTR_TZ = 0x000000000000000400;
+    public static final long VCARD_ATTR_GEO = 0x000000000000000800;
+    public static final long VCARD_ATTR_TITLE = 0x000000000000001000;
+    public static final long VCARD_ATTR_ROLE = 0x000000000000002000;
+    public static final long VCARD_ATTR_LOGO = 0x000000000000004000;
+    public static final long VCARD_ATTR_AGENT = 0x000000000000008000;
+    public static final long VCARD_ATTR_ORG = 0x000000000000010000;
+    public static final long VCARD_ATTR_NOTE = 0x000000000000020000;
+    public static final long VCARD_ATTR_REV = 0x000000000000040000;
+    public static final long VCARD_ATTR_SOUND = 0x000000000000080000;
+    public static final long VCARD_ATTR_URL = 0x000000000000100000;
+    public static final long VCARD_ATTR_UID = 0x000000000000200000;
+    public static final long VCARD_ATTR_KEY = 0x000000000000400000;
+    public static final long VCARD_ATTR_NICKNAME = 0x000000000000800000;
+    public static final long VCARD_ATTR_CATEGORIES = 0x000000000001000000;
+    public static final long VCARD_ATTR_PROID = 0x000000000002000000;
+    public static final long VCARD_ATTR_CLASS = 0x000000000004000000;
+    public static final long VCARD_ATTR_SORT_STRING = 0x000000000008000000;
+    public static final long VCARD_ATTR_X_IRMC_CALL_DATETIME =
+            0x000000000010000000;
+
+    /**
+     * Maximal number of entries of the phone book that PCE can handle
+     */
+    public static final short MAX_LIST_COUNT = (short) 0xFFFF;
+
+    /**
+     * Event propagated upon completion of <code>setPhoneBookFolderRoot</code>,
+     * <code>setPhoneBookFolderUp</code> or <code>setPhoneBookFolderDown</code>
+     * request.
+     * <p>
+     * This event indicates that request completed successfully.
+     * @see #setPhoneBookFolderRoot
+     * @see #setPhoneBookFolderUp
+     * @see #setPhoneBookFolderDown
+     */
+    public static final int EVENT_SET_PHONE_BOOK_DONE = 1;
+
+    /**
+     * Event propagated upon completion of <code>pullPhoneBook</code> request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.arg1</code></td>
+     * <td>newMissedCalls parameter (only in case of missed calls history object
+     * request)</td>
+     * </tr>
+     * <tr>
+     * <td><code>msg.obj</code></td>
+     * <td>which is a list of <code>VCardEntry</code> objects</td>
+     * </tr>
+     * </table>
+     * @see #pullPhoneBook
+     */
+    public static final int EVENT_PULL_PHONE_BOOK_DONE = 2;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardListing</code>
+     * request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.arg1</code></td>
+     * <td>newMissedCalls parameter (only in case of missed calls history object
+     * request)</td>
+     * </tr>
+     * <tr>
+     * <td><code>msg.obj</code></td>
+     * <td>which is a list of <code>BluetoothPbapCard</code> objects</td>
+     * </tr>
+     * </table>
+     * @see #pullVcardListing
+     */
+    public static final int EVENT_PULL_VCARD_LISTING_DONE = 3;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardEntry</code> request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.obj</code></td>
+     * <td>vCard as and object of type <code>VCardEntry</code></td>
+     * </tr>
+     * </table>
+     * @see #pullVcardEntry
+     */
+    public static final int EVENT_PULL_VCARD_ENTRY_DONE = 4;
+
+    /**
+     * Event propagated upon completion of <code>pullPhoneBookSize</code>
+     * request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.arg1</code></td>
+     * <td>size of the phone book</td>
+     * </tr>
+     * </table>
+     * @see #pullPhoneBookSize
+     */
+    public static final int EVENT_PULL_PHONE_BOOK_SIZE_DONE = 5;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardListingSize</code>
+     * request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.arg1</code></td>
+     * <td>size of the phone book listing</td>
+     * </tr>
+     * </table>
+     * @see #pullVcardListingSize
+     */
+    public static final int EVENT_PULL_VCARD_LISTING_SIZE_DONE = 6;
+
+    /**
+     * Event propagated upon completion of <code>setPhoneBookFolderRoot</code>,
+     * <code>setPhoneBookFolderUp</code> or <code>setPhoneBookFolderDown</code>
+     * request. This event indicates an error during operation.
+     */
+    public static final int EVENT_SET_PHONE_BOOK_ERROR = 101;
+
+    /**
+     * Event propagated upon completion of <code>pullPhoneBook</code> request.
+     * This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_PHONE_BOOK_ERROR = 102;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardListing</code>
+     * request. This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_VCARD_LISTING_ERROR = 103;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardEntry</code> request.
+     * This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_VCARD_ENTRY_ERROR = 104;
+
+    /**
+     * Event propagated upon completion of <code>pullPhoneBookSize</code>
+     * request. This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_PHONE_BOOK_SIZE_ERROR = 105;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardListingSize</code>
+     * request. This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_VCARD_LISTING_SIZE_ERROR = 106;
+
+    /**
+     * Event propagated when PCE has been connected to PSE
+     */
+    public static final int EVENT_SESSION_CONNECTED = 201;
+
+    /**
+     * Event propagated when PCE has been disconnected from PSE
+     */
+    public static final int EVENT_SESSION_DISCONNECTED = 202;
+    public static final int EVENT_SESSION_AUTH_REQUESTED = 203;
+    public static final int EVENT_SESSION_AUTH_TIMEOUT = 204;
+
+    public enum ConnectionState {
+        DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING;
+    }
+
+    private final Handler mClientHandler;
+    private final BluetoothPbapSession mSession;
+    private ConnectionState mConnectionState = ConnectionState.DISCONNECTED;
+
+    private SessionHandler mSessionHandler;
+
+    private static class SessionHandler extends Handler {
+
+        private final WeakReference<BluetoothPbapClient> mClient;
+
+        SessionHandler(BluetoothPbapClient client) {
+            mClient = new WeakReference<BluetoothPbapClient>(client);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Log.d(TAG, "handleMessage: what=" + msg.what);
+
+            BluetoothPbapClient client = mClient.get();
+            if (client == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case BluetoothPbapSession.REQUEST_FAILED:
+                {
+                    BluetoothPbapRequest req = (BluetoothPbapRequest) msg.obj;
+
+                    if (req instanceof BluetoothPbapRequestPullPhoneBookSize) {
+                        client.sendToClient(EVENT_PULL_PHONE_BOOK_SIZE_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestPullVcardListingSize) {
+                        client.sendToClient(EVENT_PULL_VCARD_LISTING_SIZE_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestPullPhoneBook) {
+                        client.sendToClient(EVENT_PULL_PHONE_BOOK_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestPullVcardListing) {
+                        client.sendToClient(EVENT_PULL_VCARD_LISTING_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestPullVcardEntry) {
+                        client.sendToClient(EVENT_PULL_VCARD_ENTRY_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestSetPath) {
+                        client.sendToClient(EVENT_SET_PHONE_BOOK_ERROR);
+                    }
+
+                    break;
+                }
+
+                case BluetoothPbapSession.REQUEST_COMPLETED:
+                {
+                    BluetoothPbapRequest req = (BluetoothPbapRequest) msg.obj;
+
+                    if (req instanceof BluetoothPbapRequestPullPhoneBookSize) {
+                        int size = ((BluetoothPbapRequestPullPhoneBookSize) req).getSize();
+                        client.sendToClient(EVENT_PULL_PHONE_BOOK_SIZE_DONE, size);
+
+                    } else if (req instanceof BluetoothPbapRequestPullVcardListingSize) {
+                        int size = ((BluetoothPbapRequestPullVcardListingSize) req).getSize();
+                        client.sendToClient(EVENT_PULL_VCARD_LISTING_SIZE_DONE, size);
+
+                    } else if (req instanceof BluetoothPbapRequestPullPhoneBook) {
+                        BluetoothPbapRequestPullPhoneBook r = (BluetoothPbapRequestPullPhoneBook) req;
+                        client.sendToClient(EVENT_PULL_PHONE_BOOK_DONE, r.getNewMissedCalls(),
+                                r.getList());
+
+                    } else if (req instanceof BluetoothPbapRequestPullVcardListing) {
+                        BluetoothPbapRequestPullVcardListing r = (BluetoothPbapRequestPullVcardListing) req;
+                        client.sendToClient(EVENT_PULL_VCARD_LISTING_DONE, r.getNewMissedCalls(),
+                                r.getList());
+
+                    } else if (req instanceof BluetoothPbapRequestPullVcardEntry) {
+                        BluetoothPbapRequestPullVcardEntry r = (BluetoothPbapRequestPullVcardEntry) req;
+                        client.sendToClient(EVENT_PULL_VCARD_ENTRY_DONE, r.getVcard());
+
+                    } else if (req instanceof BluetoothPbapRequestSetPath) {
+                        client.sendToClient(EVENT_SET_PHONE_BOOK_DONE);
+                    }
+
+                    break;
+                }
+
+                case BluetoothPbapSession.AUTH_REQUESTED:
+                    client.sendToClient(EVENT_SESSION_AUTH_REQUESTED);
+                    break;
+
+                case BluetoothPbapSession.AUTH_TIMEOUT:
+                    client.sendToClient(EVENT_SESSION_AUTH_TIMEOUT);
+                    break;
+
+                /*
+                 * app does not need to know when session is connected since
+                 * OBEX session is managed inside BluetoothPbapSession
+                 * automatically - we add this only so app can visualize PBAP
+                 * connection status in case it wants to
+                 */
+
+                case BluetoothPbapSession.SESSION_CONNECTING:
+                    client.mConnectionState = ConnectionState.CONNECTING;
+                    break;
+
+                case BluetoothPbapSession.SESSION_CONNECTED:
+                    client.mConnectionState = ConnectionState.CONNECTED;
+                    client.sendToClient(EVENT_SESSION_CONNECTED);
+                    break;
+
+                case BluetoothPbapSession.SESSION_DISCONNECTED:
+                    client.mConnectionState = ConnectionState.DISCONNECTED;
+                    client.sendToClient(EVENT_SESSION_DISCONNECTED);
+                    break;
+            }
+        }
+    };
+
+    private void sendToClient(int eventId) {
+        sendToClient(eventId, 0, null);
+    }
+
+    private void sendToClient(int eventId, int param) {
+        sendToClient(eventId, param, null);
+    }
+
+    private void sendToClient(int eventId, Object param) {
+        sendToClient(eventId, 0, param);
+    }
+
+    private void sendToClient(int eventId, int param1, Object param2) {
+        mClientHandler.obtainMessage(eventId, param1, 0, param2).sendToTarget();
+    }
+
+    /**
+     * Constructs PCE object
+     *
+     * @param device BluetoothDevice that corresponds to remote acting in PSE
+     *            role
+     * @param handler the handle that will be used by PCE to notify events and
+     *            results to application
+     * @throws NullPointerException
+     */
+    public BluetoothPbapClient(BluetoothDevice device, Handler handler) {
+        if (device == null) {
+            throw new NullPointerException("BluetothDevice is null");
+        }
+
+        mClientHandler = handler;
+
+        mSessionHandler = new SessionHandler(this);
+
+        mSession = new BluetoothPbapSession(device, mSessionHandler);
+    }
+
+    /**
+     * Starts a pbap session. <pb> This method set up rfcomm session, obex
+     * session and waits for requests to be transfered to PSE.
+     */
+    public void connect() {
+        mSession.start();
+    }
+
+    @Override
+    public void finalize() {
+        if (mSession != null) {
+            mSession.stop();
+        }
+    }
+
+    /**
+     * Stops all the active transactions and disconnects from the server.
+     */
+    public void disconnect() {
+        mSession.stop();
+    }
+
+    /**
+     * Aborts current request, if any
+     */
+    public void abort() {
+        mSession.abort();
+    }
+
+    public ConnectionState getState() {
+        return mConnectionState;
+    }
+
+    /**
+     * Sets current folder to root
+     *
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_SET_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_SET_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean setPhoneBookFolderRoot() {
+        BluetoothPbapRequest req = new BluetoothPbapRequestSetPath(false);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Sets current folder to parent
+     *
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_SET_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_SET_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean setPhoneBookFolderUp() {
+        BluetoothPbapRequest req = new BluetoothPbapRequestSetPath(true);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Sets current folder to selected sub-folder
+     *
+     * @param folder the name of the sub-folder
+     * @return @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_SET_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_SET_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean setPhoneBookFolderDown(String folder) {
+        BluetoothPbapRequest req = new BluetoothPbapRequestSetPath(folder);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Requests for the number of entries in the phone book.
+     *
+     * @param pbName absolute path to the phone book
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_SIZE_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_SIZE_ERROR} in case of failure
+     */
+    public boolean pullPhoneBookSize(String pbName) {
+        BluetoothPbapRequestPullPhoneBookSize req = new BluetoothPbapRequestPullPhoneBookSize(
+                pbName);
+
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Requests for the number of entries in the phone book listing.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_SIZE_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_SIZE_ERROR} in case of failure
+     */
+    public boolean pullVcardListingSize(String folder) {
+        BluetoothPbapRequestPullVcardListingSize req = new BluetoothPbapRequestPullVcardListingSize(
+                folder);
+
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Pulls complete phone book. This method pulls phone book which entries are
+     * of <code>VCARD_TYPE_21</code> type and each single vCard contains minimal
+     * required set of fields and the number of entries in response is not
+     * limited.
+     *
+     * @param pbName absolute path to the phone book
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean pullPhoneBook(String pbName) {
+        return pullPhoneBook(pbName, 0, VCARD_TYPE_21, 0, 0);
+    }
+
+    /**
+     * Pulls complete phone book. This method pulls all entries from the phone
+     * book.
+     *
+     * @param pbName absolute path to the phone book
+     * @param filter bit mask which indicates which fields of the vCard shall be
+     *            included in each entry of the resulting list
+     * @param format vCard format of entries in the resulting list
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean pullPhoneBook(String pbName, long filter, byte format) {
+        return pullPhoneBook(pbName, filter, format, 0, 0);
+    }
+
+    /**
+     * Pulls complete phone book. This method pulls entries from the phone book
+     * limited to the number of <code>maxListCount</code> starting from the
+     * position of <code>listStartOffset</code>.
+     * <p>
+     * The resulting list contains vCard objects in version
+     * <code>VCARD_TYPE_21</code> which in turns contain minimal required set of
+     * vCard fields.
+     *
+     * @param pbName absolute path to the phone book
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean pullPhoneBook(String pbName, int maxListCount, int listStartOffset) {
+        return pullPhoneBook(pbName, 0, VCARD_TYPE_21, maxListCount, listStartOffset);
+    }
+
+    /**
+     * Pulls complete phone book.
+     *
+     * @param pbName absolute path to the phone book
+     * @param filter bit mask which indicates which fields of the vCard hall be
+     *            included in each entry of the resulting list
+     * @param format vCard format of entries in the resulting list
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean pullPhoneBook(String pbName, long filter, byte format, int maxListCount,
+            int listStartOffset) {
+        BluetoothPbapRequest req = new BluetoothPbapRequestPullPhoneBook(pbName, filter, format,
+                maxListCount, listStartOffset);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Pulls list of entries in the phone book.
+     * <p>
+     * This method pulls the list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder) {
+        return pullVcardListing(folder, ORDER_BY_DEFAULT, SEARCH_ATTR_NAME, null, 0, 0);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param order the sorting order of the resulting list of entries
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, byte order) {
+        return pullVcardListing(folder, order, SEARCH_ATTR_NAME, null, 0, 0);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>. Only entries where
+     * <code>searchAttr</code> attribute of vCard matches <code>searchVal</code>
+     * will be listed.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param searchAttr vCard attribute which shall be used to carry out search
+     *            operation on
+     * @param searchVal text string used by matching routine to match the value
+     *            of the attribute indicated by SearchAttr
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, byte searchAttr, String searchVal) {
+        return pullVcardListing(folder, ORDER_BY_DEFAULT, searchAttr, searchVal, 0, 0);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param order the sorting order of the resulting list of entries
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, byte order, int maxListCount,
+            int listStartOffset) {
+        return pullVcardListing(folder, order, SEARCH_ATTR_NAME, null, maxListCount,
+                listStartOffset);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, int maxListCount, int listStartOffset) {
+        return pullVcardListing(folder, ORDER_BY_DEFAULT, SEARCH_ATTR_NAME, null, maxListCount,
+                listStartOffset);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param order the sorting order of the resulting list of entries
+     * @param searchAttr vCard attribute which shall be used to carry out search
+     *            operation on
+     * @param searchVal text string used by matching routine to match the value
+     *            of the attribute indicated by SearchAttr
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, byte order, byte searchAttr,
+            String searchVal, int maxListCount, int listStartOffset) {
+        BluetoothPbapRequest req = new BluetoothPbapRequestPullVcardListing(folder, order,
+                searchAttr, searchVal, maxListCount, listStartOffset);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Pulls single vCard object
+     *
+     * @param handle handle to the vCard which shall be pulled
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_DONE} or
+     * @link #EVENT_PULL_VCARD_ERROR} in case of failure
+     */
+    public boolean pullVcardEntry(String handle) {
+        return pullVcardEntry(handle, (byte) 0, VCARD_TYPE_21);
+    }
+
+    /**
+     * Pulls single vCard object
+     *
+     * @param handle handle to the vCard which shall be pulled
+     * @param filter bit mask of the vCard fields that shall be included in the
+     *            resulting vCard
+     * @param format resulting vCard version
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_DONE}
+     * @link #EVENT_PULL_VCARD_ERROR} in case of failure
+     */
+    public boolean pullVcardEntry(String handle, long filter, byte format) {
+        BluetoothPbapRequest req = new BluetoothPbapRequestPullVcardEntry(handle, filter, format);
+        return mSession.makeRequest(req);
+    }
+
+    public boolean setAuthResponse(String key) {
+        Log.d(TAG, " setAuthResponse key=" + key);
+        return mSession.setAuthResponse(key);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
new file mode 100644
index 0000000..239e675
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.os.Handler;
+import android.util.Log;
+
+import javax.obex.Authenticator;
+import javax.obex.PasswordAuthentication;
+
+class BluetoothPbapObexAuthenticator implements Authenticator {
+
+    private final static String TAG = "BluetoothPbapObexAuthenticator";
+
+    private String mSessionKey;
+
+    private boolean mReplied;
+
+    private final Handler mCallback;
+
+    public BluetoothPbapObexAuthenticator(Handler callback) {
+        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");
+        mCallback.obtainMessage(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_REQUEST)
+                .sendToTarget();
+
+        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");
+                }
+            }
+        }
+
+        if (mSessionKey != null && mSessionKey.length() != 0) {
+            Log.v(TAG, "onAuthenticationChallenge: mSessionKey=" + mSessionKey);
+            pa = new PasswordAuthentication(null, mSessionKey.getBytes());
+        } else {
+            Log.v(TAG, "onAuthenticationChallenge: mSessionKey is empty, timeout/cancel occured");
+        }
+
+        return pa;
+    }
+
+    @Override
+    public byte[] onAuthenticationResponse(byte[] userName) {
+        /* required only in case PCE challenges PSE which we don't do now */
+        return null;
+    }
+
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexSession.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexSession.java
new file mode 100644
index 0000000..d7410cf
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexSession.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Motorola, Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ObexTransport;
+import javax.obex.ResponseCodes;
+
+final class BluetoothPbapObexSession {
+    private static final String TAG = "BluetoothPbapObexSession";
+
+    private static final byte[] PBAP_TARGET = new byte[] {
+            0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66,
+            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
+    };
+
+    final static int OBEX_SESSION_CONNECTED = 100;
+    final static int OBEX_SESSION_FAILED = 101;
+    final static int OBEX_SESSION_DISCONNECTED = 102;
+    final static int OBEX_SESSION_REQUEST_COMPLETED = 103;
+    final static int OBEX_SESSION_REQUEST_FAILED = 104;
+    final static int OBEX_SESSION_AUTHENTICATION_REQUEST = 105;
+    final static int OBEX_SESSION_AUTHENTICATION_TIMEOUT = 106;
+
+    private Handler mSessionHandler;
+    private final ObexTransport mTransport;
+    private ObexClientThread mObexClientThread;
+    private BluetoothPbapObexAuthenticator mAuth = null;
+
+    public BluetoothPbapObexSession(ObexTransport transport) {
+        mTransport = transport;
+    }
+
+    public void start(Handler handler) {
+        Log.d(TAG, "start");
+        mSessionHandler = handler;
+
+        mAuth = new BluetoothPbapObexAuthenticator(mSessionHandler);
+
+        mObexClientThread = new ObexClientThread();
+        mObexClientThread.start();
+    }
+
+    public void stop() {
+        Log.d(TAG, "stop");
+
+        if (mObexClientThread != null) {
+            try {
+                mObexClientThread.interrupt();
+                mObexClientThread.join();
+                mObexClientThread = null;
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    public void abort() {
+        Log.d(TAG, "abort");
+
+        if (mObexClientThread != null && mObexClientThread.mRequest != null) {
+            /*
+             * since abort may block until complete GET is processed inside OBEX
+             * session, let's run it in separate thread so it won't block UI
+             */
+            (new Thread() {
+                @Override
+                public void run() {
+                    mObexClientThread.mRequest.abort();
+                }
+            }).run();
+        }
+    }
+
+    public boolean schedule(BluetoothPbapRequest request) {
+        Log.d(TAG, "schedule: " + request.getClass().getSimpleName());
+
+        if (mObexClientThread == null) {
+            Log.e(TAG, "OBEX session not started");
+            return false;
+        }
+
+        return mObexClientThread.schedule(request);
+    }
+
+    public boolean setAuthReply(String key) {
+        Log.d(TAG, "setAuthReply key=" + key);
+
+        if (mAuth == null) {
+            return false;
+        }
+
+        mAuth.setReply(key);
+
+        return true;
+    }
+
+    private class ObexClientThread extends Thread {
+
+        private static final String TAG = "ObexClientThread";
+
+        private ClientSession mClientSession;
+        private BluetoothPbapRequest mRequest;
+
+        private volatile boolean mRunning = true;
+
+        public ObexClientThread() {
+
+            mClientSession = null;
+            mRequest = null;
+        }
+
+        @Override
+        public void run() {
+            super.run();
+
+            if (!connect()) {
+                mSessionHandler.obtainMessage(OBEX_SESSION_FAILED).sendToTarget();
+                return;
+            }
+
+            mSessionHandler.obtainMessage(OBEX_SESSION_CONNECTED).sendToTarget();
+
+            while (mRunning) {
+                synchronized (this) {
+                    try {
+                        if (mRequest == null) {
+                            this.wait();
+                        }
+                    } catch (InterruptedException e) {
+                        mRunning = false;
+                        break;
+                    }
+                }
+
+                if (mRunning && mRequest != null) {
+                    try {
+                        mRequest.execute(mClientSession);
+                    } catch (IOException e) {
+                        // this will "disconnect" for cleanup
+                        mRunning = false;
+                    }
+
+                    if (mRequest.isSuccess()) {
+                        mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_COMPLETED, mRequest)
+                                .sendToTarget();
+                    } else {
+                        mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_FAILED, mRequest)
+                                .sendToTarget();
+                    }
+                }
+
+                mRequest = null;
+            }
+
+            disconnect();
+
+            mSessionHandler.obtainMessage(OBEX_SESSION_DISCONNECTED).sendToTarget();
+        }
+
+        public synchronized boolean schedule(BluetoothPbapRequest request) {
+            Log.d(TAG, "schedule: " + request.getClass().getSimpleName());
+
+            if (mRequest != null) {
+                return false;
+            }
+
+            mRequest = request;
+            notify();
+
+            return true;
+        }
+
+        private boolean connect() {
+            Log.d(TAG, "connect");
+
+            try {
+                mClientSession = new ClientSession(mTransport);
+                mClientSession.setAuthenticator(mAuth);
+            } catch (IOException e) {
+                return false;
+            }
+
+            HeaderSet hs = new HeaderSet();
+            hs.setHeader(HeaderSet.TARGET, PBAP_TARGET);
+
+            try {
+                hs = mClientSession.connect(hs);
+
+                if (hs.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
+                    disconnect();
+                    return false;
+                }
+            } catch (IOException e) {
+                return false;
+            }
+
+            return true;
+        }
+
+        private void disconnect() {
+            Log.d(TAG, "disconnect");
+
+            if (mClientSession != null) {
+                try {
+                    mClientSession.disconnect(null);
+                    mClientSession.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexTransport.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexTransport.java
new file mode 100644
index 0000000..8576636
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexTransport.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Motorola, Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.bluetooth.BluetoothSocket;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.obex.ObexTransport;
+
+class BluetoothPbapObexTransport implements ObexTransport {
+
+    private BluetoothSocket mSocket = null;
+
+    public BluetoothPbapObexTransport(BluetoothSocket rfs) {
+        super();
+        mSocket = rfs;
+    }
+
+    @Override
+    public void close() throws IOException {
+        mSocket.close();
+    }
+
+    @Override
+    public DataInputStream openDataInputStream() throws IOException {
+        return new DataInputStream(openInputStream());
+    }
+
+    @Override
+    public DataOutputStream openDataOutputStream() throws IOException {
+        return new DataOutputStream(openOutputStream());
+    }
+
+    @Override
+    public InputStream openInputStream() throws IOException {
+        return mSocket.getInputStream();
+    }
+
+    @Override
+    public OutputStream openOutputStream() throws IOException {
+        return mSocket.getOutputStream();
+    }
+
+    @Override
+    public void connect() throws IOException {
+    }
+
+    @Override
+    public void create() throws IOException {
+    }
+
+    @Override
+    public void disconnect() throws IOException {
+    }
+
+    @Override
+    public void listen() throws IOException {
+    }
+
+    public boolean isConnected() throws IOException {
+        // return true;
+        return mSocket.isConnected();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequest.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequest.java
new file mode 100644
index 0000000..5f73ffc
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.obex.ClientOperation;
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+abstract class BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequest";
+
+    protected static final byte OAP_TAGID_ORDER = 0x01;
+    protected static final byte OAP_TAGID_SEARCH_VALUE = 0x02;
+    protected static final byte OAP_TAGID_SEARCH_ATTRIBUTE = 0x03;
+    protected static final byte OAP_TAGID_MAX_LIST_COUNT = 0x04;
+    protected static final byte OAP_TAGID_LIST_START_OFFSET = 0x05;
+    protected static final byte OAP_TAGID_FILTER = 0x06;
+    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 HeaderSet mHeaderSet;
+
+    protected int mResponseCode;
+
+    private boolean mAborted = false;
+
+    private ClientOperation mOp = null;
+
+    public BluetoothPbapRequest() {
+        mHeaderSet = new HeaderSet();
+    }
+
+    final public boolean isSuccess() {
+        return (mResponseCode == ResponseCodes.OBEX_HTTP_OK);
+    }
+
+    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;
+        }
+
+        try {
+            mOp = (ClientOperation) session.get(mHeaderSet);
+
+            /* make sure final flag for GET is used (PBAP spec 6.2.2) */
+            mOp.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();
+
+            mResponseCode = mOp.getResponseCode();
+
+            Log.d(TAG, "mResponseCode=" + mResponseCode);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException occured when processing request", e);
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+
+            throw e;
+        }
+    }
+
+    public void abort() {
+        mAborted = true;
+
+        if (mOp != null) {
+            try {
+                mOp.abort();
+            } catch (IOException e) {
+                Log.e(TAG, "Exception occured when trying to abort", e);
+            }
+        }
+    }
+
+    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 dafault */
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
new file mode 100644
index 0000000..db08189
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullPhoneBook extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullPhoneBook";
+
+    private static final String TYPE = "x-bt/phonebook";
+
+    private BluetoothPbapVcardList mResponse;
+
+    private int mNewMissedCalls = -1;
+
+    private final byte mFormat;
+
+    public BluetoothPbapRequestPullPhoneBook(String pbName, long filter, byte format,
+            int maxListCount, int listStartOffset) {
+
+        if (maxListCount < 0 || maxListCount > 65535) {
+            throw new IllegalArgumentException("maxListCount should be [0..65535]");
+        }
+
+        if (listStartOffset < 0 || listStartOffset > 65535) {
+            throw new IllegalArgumentException("listStartOffset should be [0..65535]");
+        }
+
+        mHeaderSet.setHeader(HeaderSet.NAME, pbName);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        /* make sure format is one of allowed values */
+        if (format != BluetoothPbapClient.VCARD_TYPE_21
+                && format != BluetoothPbapClient.VCARD_TYPE_30) {
+            format = BluetoothPbapClient.VCARD_TYPE_21;
+        }
+
+        if (filter != 0) {
+            oap.add(OAP_TAGID_FILTER, filter);
+        }
+
+        oap.add(OAP_TAGID_FORMAT, format);
+
+        /*
+         * maxListCount is a special case which is handled in
+         * BluetoothPbapRequestPullPhoneBookSize
+         */
+        if (maxListCount > 0) {
+            oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) maxListCount);
+        } else {
+            oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 65535);
+        }
+
+        if (listStartOffset > 0) {
+            oap.add(OAP_TAGID_LIST_START_OFFSET, (short) listStartOffset);
+        }
+
+        oap.addToHeaderSet(mHeaderSet);
+
+        mFormat = format;
+    }
+
+    @Override
+    protected void readResponse(InputStream stream) throws IOException {
+        Log.v(TAG, "readResponse");
+
+        mResponse = new BluetoothPbapVcardList(stream, mFormat);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        Log.v(TAG, "readResponse");
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        if (oap.exists(OAP_TAGID_NEW_MISSED_CALLS)) {
+            mNewMissedCalls = oap.getByte(OAP_TAGID_NEW_MISSED_CALLS);
+        }
+    }
+
+    public ArrayList<VCardEntry> getList() {
+        return mResponse.getList();
+    }
+
+    public int getNewMissedCalls() {
+        return mNewMissedCalls;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
new file mode 100644
index 0000000..8ad2324
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import javax.obex.HeaderSet;
+
+class BluetoothPbapRequestPullPhoneBookSize extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullPhoneBookSize";
+
+    private static final String TYPE = "x-bt/phonebook";
+
+    private int mSize;
+
+    public BluetoothPbapRequestPullPhoneBookSize(String pbName) {
+        mHeaderSet.setHeader(HeaderSet.NAME, pbName);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+        oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0);
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        Log.v(TAG, "readResponseHeaders");
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE);
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardEntry.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardEntry.java
new file mode 100644
index 0000000..1bbe359
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardEntry.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullVcardEntry extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullVcardEntry";
+
+    private static final String TYPE = "x-bt/vcard";
+
+    private BluetoothPbapVcardList mResponse;
+
+    private final byte mFormat;
+
+    public BluetoothPbapRequestPullVcardEntry(String handle, long filter, byte format) {
+        mHeaderSet.setHeader(HeaderSet.NAME, handle);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        /* make sure format is one of allowed values */
+        if (format != BluetoothPbapClient.VCARD_TYPE_21
+                && format != BluetoothPbapClient.VCARD_TYPE_30) {
+            format = BluetoothPbapClient.VCARD_TYPE_21;
+        }
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        if (filter != 0) {
+            oap.add(OAP_TAGID_FILTER, filter);
+        }
+
+        oap.add(OAP_TAGID_FORMAT, format);
+        oap.addToHeaderSet(mHeaderSet);
+
+        mFormat = format;
+    }
+
+    @Override
+    protected void readResponse(InputStream stream) throws IOException {
+        Log.v(TAG, "readResponse");
+
+        mResponse = new BluetoothPbapVcardList(stream, mFormat);
+
+        if (mResponse.getCount() == 0) {
+            throw new IOException("Invalid response received");
+        }
+    }
+
+    public VCardEntry getVcard() {
+        return mResponse.getFirst();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListing.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListing.java
new file mode 100644
index 0000000..a6863c4
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListing.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+import org.codeaurora.bluetooth.pbapclient.BluetoothPbapVcardListing;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullVcardListing extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullVcardListing";
+
+    private static final String TYPE = "x-bt/vcard-listing";
+
+    private BluetoothPbapVcardListing mResponse = null;
+
+    private int mNewMissedCalls = -1;
+
+    public BluetoothPbapRequestPullVcardListing(String folder, byte order, byte searchAttr,
+            String searchVal, int maxListCount, int listStartOffset) {
+
+        if (maxListCount < 0 || maxListCount > 65535) {
+            throw new IllegalArgumentException("maxListCount should be [0..65535]");
+        }
+
+        if (listStartOffset < 0 || listStartOffset > 65535) {
+            throw new IllegalArgumentException("listStartOffset should be [0..65535]");
+        }
+
+        if (folder == null) {
+            folder = "";
+        }
+
+        mHeaderSet.setHeader(HeaderSet.NAME, folder);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        if (order >= 0) {
+            oap.add(OAP_TAGID_ORDER, order);
+        }
+
+        if (searchVal != null) {
+            oap.add(OAP_TAGID_SEARCH_ATTRIBUTE, searchAttr);
+            oap.add(OAP_TAGID_SEARCH_VALUE, searchVal);
+        }
+
+        /*
+         * maxListCount is a special case which is handled in
+         * BluetoothPbapRequestPullVcardListingSize
+         */
+        if (maxListCount > 0) {
+            oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) maxListCount);
+        }
+
+        if (listStartOffset > 0) {
+            oap.add(OAP_TAGID_LIST_START_OFFSET, (short) listStartOffset);
+        }
+
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponse(InputStream stream) throws IOException {
+        Log.v(TAG, "readResponse");
+
+        mResponse = new BluetoothPbapVcardListing(stream);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        Log.v(TAG, "readResponseHeaders");
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        if (oap.exists(OAP_TAGID_NEW_MISSED_CALLS)) {
+            mNewMissedCalls = oap.getByte(OAP_TAGID_NEW_MISSED_CALLS);
+        }
+    }
+
+    public ArrayList<BluetoothPbapCard> getList() {
+        return mResponse.getList();
+    }
+
+    public int getNewMissedCalls() {
+        return mNewMissedCalls;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListingSize.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListingSize.java
new file mode 100644
index 0000000..9fb9295
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListingSize.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import javax.obex.HeaderSet;
+
+class BluetoothPbapRequestPullVcardListingSize extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullVcardListingSize";
+
+    private static final String TYPE = "x-bt/vcard-listing";
+
+    private int mSize;
+
+    public BluetoothPbapRequestPullVcardListingSize(String folder) {
+        mHeaderSet.setHeader(HeaderSet.NAME, folder);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+        oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0);
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        Log.v(TAG, "readResponseHeaders");
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE);
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestSetPath.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestSetPath.java
new file mode 100644
index 0000000..ca6d9b5
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestSetPath.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+final class BluetoothPbapRequestSetPath extends BluetoothPbapRequest {
+
+    private final static String TAG = "BluetoothPbapRequestSetPath";
+
+    private enum SetPathDir {
+        ROOT, UP, DOWN
+    };
+
+    private SetPathDir mDir;
+
+    public BluetoothPbapRequestSetPath(String name) {
+        mDir = SetPathDir.DOWN;
+        mHeaderSet.setHeader(HeaderSet.NAME, name);
+    }
+
+    public BluetoothPbapRequestSetPath(boolean goUp) {
+        mHeaderSet.setEmptyNameHeader();
+        if (goUp) {
+            mDir = SetPathDir.UP;
+        } else {
+            mDir = SetPathDir.ROOT;
+        }
+    }
+
+    @Override
+    public void execute(ClientSession session) {
+        Log.v(TAG, "execute");
+
+        HeaderSet hs = null;
+
+        try {
+            switch (mDir) {
+                case ROOT:
+                case DOWN:
+                    hs = session.setPath(mHeaderSet, false, false);
+                    break;
+                case UP:
+                    hs = session.setPath(mHeaderSet, true, false);
+                    break;
+            }
+
+            mResponseCode = hs.getResponseCode();
+        } catch (IOException e) {
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        }
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapSession.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapSession.java
new file mode 100644
index 0000000..7cd16f1
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapSession.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.UUID;
+
+class BluetoothPbapSession implements Callback {
+    private static final String TAG = "BluetoothPbapSession";
+
+    /* local use only */
+    private static final int RFCOMM_CONNECTED = 1;
+    private static final int RFCOMM_FAILED = 2;
+
+    /* to BluetoothPbapClient */
+    public static final int REQUEST_COMPLETED = 3;
+    public static final int REQUEST_FAILED = 4;
+    public static final int SESSION_CONNECTING = 5;
+    public static final int SESSION_CONNECTED = 6;
+    public static final int SESSION_DISCONNECTED = 7;
+    public static final int AUTH_REQUESTED = 8;
+    public static final int AUTH_TIMEOUT = 9;
+
+    public static final int ACTION_LISTING = 14;
+    public static final int ACTION_VCARD = 15;
+    public static final int ACTION_PHONEBOOK_SIZE = 16;
+
+    private static final String PBAP_UUID =
+            "0000112f-0000-1000-8000-00805f9b34fb";
+
+    private final BluetoothAdapter mAdapter;
+    private final BluetoothDevice mDevice;
+
+    private final Handler mParentHandler;
+
+    private final HandlerThread mHandlerThread;
+    private final Handler mSessionHandler;
+
+    private RfcommConnectThread mConnectThread;
+    private BluetoothPbapObexTransport mTransport;
+
+    private BluetoothPbapObexSession mObexSession;
+
+    private BluetoothPbapRequest mPendingRequest = null;
+
+    public BluetoothPbapSession(BluetoothDevice device, Handler handler) {
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mAdapter == null) {
+            throw new NullPointerException("No Bluetooth adapter in the system");
+        }
+
+        mDevice = device;
+        mParentHandler = handler;
+        mConnectThread = null;
+        mTransport = null;
+        mObexSession = null;
+
+        mHandlerThread = new HandlerThread("PBAP session handler",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        mHandlerThread.start();
+        mSessionHandler = new Handler(mHandlerThread.getLooper(), this);
+    }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        Log.d(TAG, "Handler: msg: " + msg.what);
+
+        switch (msg.what) {
+            case RFCOMM_FAILED:
+                mConnectThread = null;
+
+                mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
+
+                if (mPendingRequest != null) {
+                    mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
+                    mPendingRequest = null;
+                }
+                break;
+
+            case RFCOMM_CONNECTED:
+                mConnectThread = null;
+                mTransport = (BluetoothPbapObexTransport) msg.obj;
+                startObexSession();
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_FAILED:
+                stopObexSession();
+
+                mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
+
+                if (mPendingRequest != null) {
+                    mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
+                    mPendingRequest = null;
+                }
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_CONNECTED:
+                mParentHandler.obtainMessage(SESSION_CONNECTED).sendToTarget();
+
+                if (mPendingRequest != null) {
+                    mObexSession.schedule(mPendingRequest);
+                    mPendingRequest = null;
+                }
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_DISCONNECTED:
+                mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
+                stopRfcomm();
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_COMPLETED:
+                /* send to parent, process there */
+                mParentHandler.obtainMessage(REQUEST_COMPLETED, msg.obj).sendToTarget();
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_FAILED:
+                /* send to parent, process there */
+                mParentHandler.obtainMessage(REQUEST_FAILED, msg.obj).sendToTarget();
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_REQUEST:
+                /* send to parent, process there */
+                mParentHandler.obtainMessage(AUTH_REQUESTED).sendToTarget();
+
+                mSessionHandler
+                        .sendMessageDelayed(
+                                mSessionHandler
+                                        .obtainMessage(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT),
+                                30000);
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT:
+                /* stop authentication */
+                setAuthResponse(null);
+
+                mParentHandler.obtainMessage(AUTH_TIMEOUT).sendToTarget();
+                break;
+
+            default:
+                return false;
+        }
+
+        return true;
+    }
+
+    public void start() {
+        Log.d(TAG, "start");
+
+        startRfcomm();
+    }
+
+    public void stop() {
+        Log.d(TAG, "Stop");
+
+        stopObexSession();
+        stopRfcomm();
+    }
+
+    public void abort() {
+        Log.d(TAG, "abort");
+
+        /* fail pending request immediately */
+        if (mPendingRequest != null) {
+            mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
+            mPendingRequest = null;
+        }
+
+        if (mObexSession != null) {
+            mObexSession.abort();
+        }
+    }
+
+    public boolean makeRequest(BluetoothPbapRequest request) {
+        Log.v(TAG, "makeRequest: " + request.getClass().getSimpleName());
+
+        if (mPendingRequest != null) {
+            Log.w(TAG, "makeRequest: request already queued, exiting");
+            return false;
+        }
+
+        if (mObexSession == null) {
+            mPendingRequest = request;
+
+            /*
+             * since there is no pending request and no session it's safe to
+             * assume that RFCOMM does not exist either and we should start
+             * connecting it
+             */
+            startRfcomm();
+
+            return true;
+        }
+
+        return mObexSession.schedule(request);
+    }
+
+    public boolean setAuthResponse(String key) {
+        Log.d(TAG, "setAuthResponse key=" + key);
+
+        mSessionHandler
+                .removeMessages(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT);
+
+        /* does not make sense to set auth response when OBEX session is down */
+        if (mObexSession == null) {
+            return false;
+        }
+
+        return mObexSession.setAuthReply(key);
+    }
+
+    private void startRfcomm() {
+        Log.d(TAG, "startRfcomm");
+
+        if (mConnectThread == null && mObexSession == null) {
+            mParentHandler.obtainMessage(SESSION_CONNECTING).sendToTarget();
+
+            mConnectThread = new RfcommConnectThread();
+            mConnectThread.start();
+        }
+
+        /*
+         * don't care if mConnectThread is not null - it means RFCOMM is being
+         * connected anyway
+         */
+    }
+
+    private void stopRfcomm() {
+        Log.d(TAG, "stopRfcomm");
+
+        if (mConnectThread != null) {
+            try {
+                mConnectThread.join();
+            } catch (InterruptedException e) {
+            }
+
+            mConnectThread = null;
+        }
+
+        if (mTransport != null) {
+            try {
+                mTransport.close();
+            } catch (IOException e) {
+            }
+
+            mTransport = null;
+        }
+    }
+
+    private void startObexSession() {
+        Log.d(TAG, "startObexSession");
+
+        mObexSession = new BluetoothPbapObexSession(mTransport);
+        mObexSession.start(mSessionHandler);
+    }
+
+    private void stopObexSession() {
+        Log.d(TAG, "stopObexSession");
+
+        if (mObexSession != null) {
+            mObexSession.stop();
+            mObexSession = null;
+        }
+    }
+
+    private class RfcommConnectThread extends Thread {
+        private static final String TAG = "RfcommConnectThread";
+
+        private BluetoothSocket mSocket;
+
+        public RfcommConnectThread() {
+            super("RfcommConnectThread");
+        }
+
+        @Override
+        public void run() {
+            if (mAdapter.isDiscovering()) {
+                mAdapter.cancelDiscovery();
+            }
+
+            try {
+                mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(PBAP_UUID));
+                mSocket.connect();
+
+                BluetoothPbapObexTransport transport;
+                transport = new BluetoothPbapObexTransport(mSocket);
+
+                mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
+            } catch (IOException e) {
+                closeSocket();
+                mSessionHandler.obtainMessage(RFCOMM_FAILED).sendToTarget();
+            }
+
+        }
+
+        private void closeSocket() {
+            try {
+                if (mSocket != null) {
+                    mSocket.close();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Error when closing socket", e);
+            }
+        }
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardList.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardList.java
new file mode 100644
index 0000000..35118d8
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardList.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardEntryCounter;
+import com.android.vcard.VCardEntryHandler;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+class BluetoothPbapVcardList {
+
+    private final ArrayList<VCardEntry> mCards = new ArrayList<VCardEntry>();
+
+    class CardEntryHandler implements VCardEntryHandler {
+        @Override
+        public void onStart() {
+        }
+
+        @Override
+        public void onEntryCreated(VCardEntry entry) {
+            mCards.add(entry);
+        }
+
+        @Override
+        public void onEnd() {
+        }
+    }
+
+    public BluetoothPbapVcardList(InputStream in, byte format) throws IOException {
+        parse(in, format);
+    }
+
+    private void parse(InputStream in, byte format) throws IOException {
+        VCardParser parser;
+
+        if (format == BluetoothPbapClient.VCARD_TYPE_30) {
+            parser = new VCardParser_V30();
+        } else {
+            parser = new VCardParser_V21();
+        }
+
+        VCardEntryConstructor constructor = new VCardEntryConstructor();
+        VCardEntryCounter counter = new VCardEntryCounter();
+        CardEntryHandler handler = new CardEntryHandler();
+
+        constructor.addEntryHandler(handler);
+
+        parser.addInterpreter(constructor);
+        parser.addInterpreter(counter);
+
+        try {
+            parser.parse(in);
+        } catch (VCardException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public int getCount() {
+        return mCards.size();
+    }
+
+    public ArrayList<VCardEntry> getList() {
+        return mCards;
+    }
+
+    public VCardEntry getFirst() {
+        return mCards.get(0);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardListing.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardListing.java
new file mode 100644
index 0000000..2b633ec
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardListing.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+class BluetoothPbapVcardListing {
+
+    private static final String TAG = "BluetoothPbapVcardListing";
+
+    ArrayList<BluetoothPbapCard> mCards = new ArrayList<BluetoothPbapCard>();
+
+    public BluetoothPbapVcardListing(InputStream in) throws IOException {
+        parse(in);
+    }
+
+    private void parse(InputStream in) throws IOException {
+        XmlPullParser parser = Xml.newPullParser();
+
+        try {
+            parser.setInput(in, "UTF-8");
+
+            int eventType = parser.getEventType();
+
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+
+                if (eventType == XmlPullParser.START_TAG && parser.getName().equals("card")) {
+                    BluetoothPbapCard card = new BluetoothPbapCard(
+                            parser.getAttributeValue(null, "handle"),
+                            parser.getAttributeValue(null, "name"));
+                    mCards.add(card);
+                }
+
+                eventType = parser.next();
+            }
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "XML parser error when parsing XML", e);
+        }
+    }
+
+    public ArrayList<BluetoothPbapCard> getList() {
+        return mCards;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/utils/BmsgTokenizer.java b/src/org/codeaurora/bluetooth/pbapclient/utils/BmsgTokenizer.java
new file mode 100644
index 0000000..8e1bbe4
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/utils/BmsgTokenizer.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.utils;
+
+import android.util.Log;
+
+import java.text.ParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class BmsgTokenizer {
+
+    private final String mStr;
+
+    private final Matcher mMatcher;
+
+    private int mPos = 0;
+
+    private final int mOffset;
+
+    static public class Property {
+        public final String name;
+        public final String value;
+
+        public Property(String name, String value) {
+            if (name == null || value == null) {
+                throw new IllegalArgumentException();
+            }
+
+            this.name = name;
+            this.value = value;
+
+            Log.v("BMSG >> ", toString());
+        }
+
+        @Override
+        public String toString() {
+            return name + ":" + value;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return ((o instanceof Property) && ((Property) o).name.equals(name) && ((Property) o).value
+                    .equals(value));
+        }
+    };
+
+    public BmsgTokenizer(String str) {
+        this(str, 0);
+    }
+
+    public BmsgTokenizer(String str, int offset) {
+        mStr = str;
+        mOffset = offset;
+        mMatcher = Pattern.compile("(([^:]*):(.*))?\r\n").matcher(str);
+        mPos = mMatcher.regionStart();
+    }
+
+    public Property next(boolean alwaysReturn) throws ParseException {
+        boolean found = false;
+
+        do {
+            mMatcher.region(mPos, mMatcher.regionEnd());
+
+            if (!mMatcher.lookingAt()) {
+                if (alwaysReturn) {
+                    return null;
+                }
+
+                throw new ParseException("Property or empty line expected", pos());
+            }
+
+            mPos = mMatcher.end();
+
+            if (mMatcher.group(1) != null) {
+                found = true;
+            }
+        } while (!found);
+
+        return new Property(mMatcher.group(2), mMatcher.group(3));
+    }
+
+    public Property next() throws ParseException {
+        return next(false);
+    }
+
+    public String remaining() {
+        return mStr.substring(mPos);
+    }
+
+    public int pos() {
+        return mPos + mOffset;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/utils/ObexAppParameters.java b/src/org/codeaurora/bluetooth/pbapclient/utils/ObexAppParameters.java
new file mode 100644
index 0000000..cfb7667
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/utils/ObexAppParameters.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.utils;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.obex.HeaderSet;
+
+public final class ObexAppParameters {
+
+    private final HashMap<Byte, byte[]> mParams;
+
+    public ObexAppParameters() {
+        mParams = new HashMap<Byte, byte[]>();
+    }
+
+    public ObexAppParameters(byte[] raw) {
+        mParams = new HashMap<Byte, byte[]>();
+
+        if (raw != null) {
+            for (int i = 0; i < raw.length;) {
+                if (raw.length - i < 2) {
+                    break;
+                }
+
+                byte tag = raw[i++];
+                byte len = raw[i++];
+
+                if (raw.length - i - len < 0) {
+                    break;
+                }
+
+                byte[] val = new byte[len];
+
+                System.arraycopy(raw, i, val, 0, len);
+                this.add(tag, val);
+
+                i += len;
+            }
+        }
+    }
+
+    public static ObexAppParameters fromHeaderSet(HeaderSet headerset) {
+        try {
+            byte[] raw = (byte[]) headerset.getHeader(HeaderSet.APPLICATION_PARAMETER);
+            return new ObexAppParameters(raw);
+        } catch (IOException e) {
+            // won't happen
+        }
+
+        return null;
+    }
+
+    public byte[] getHeader() {
+        int length = 0;
+
+        for (Map.Entry<Byte, byte[]> entry : mParams.entrySet()) {
+            length += (entry.getValue().length + 2);
+        }
+
+        byte[] ret = new byte[length];
+
+        int idx = 0;
+        for (Map.Entry<Byte, byte[]> entry : mParams.entrySet()) {
+            length = entry.getValue().length;
+
+            ret[idx++] = entry.getKey();
+            ret[idx++] = (byte) length;
+            System.arraycopy(entry.getValue(), 0, ret, idx, length);
+            idx += length;
+        }
+
+        return ret;
+    }
+
+    public void addToHeaderSet(HeaderSet headerset) {
+        if (mParams.size() > 0) {
+            headerset.setHeader(HeaderSet.APPLICATION_PARAMETER, getHeader());
+        }
+    }
+
+    public boolean exists(byte tag) {
+        return mParams.containsKey(tag);
+    }
+
+    public void add(byte tag, byte val) {
+        byte[] bval = ByteBuffer.allocate(1).put(val).array();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, short val) {
+        byte[] bval = ByteBuffer.allocate(2).putShort(val).array();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, int val) {
+        byte[] bval = ByteBuffer.allocate(4).putInt(val).array();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, long val) {
+        byte[] bval = ByteBuffer.allocate(8).putLong(val).array();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, String val) {
+        byte[] bval = val.getBytes();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, byte[] bval) {
+        mParams.put(tag, bval);
+    }
+
+    public byte getByte(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        if (bval == null || bval.length < 1) {
+            return 0;
+        }
+
+        return ByteBuffer.wrap(bval).get();
+    }
+
+    public short getShort(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        if (bval == null || bval.length < 2) {
+            return 0;
+        }
+
+        return ByteBuffer.wrap(bval).getShort();
+    }
+
+    public int getInt(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        if (bval == null || bval.length < 4) {
+            return 0;
+        }
+
+        return ByteBuffer.wrap(bval).getInt();
+    }
+
+    public String getString(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        if (bval == null) {
+            return null;
+        }
+
+        return new String(bval);
+    }
+
+    public byte[] getByteArray(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        return bval;
+    }
+
+    @Override
+    public String toString() {
+        return mParams.toString();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/utils/ObexTime.java b/src/org/codeaurora/bluetooth/pbapclient/utils/ObexTime.java
new file mode 100644
index 0000000..83c42ad
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/utils/ObexTime.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.utils;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class ObexTime {
+
+    private Date mDate;
+
+    public ObexTime(String time) {
+        /*
+         * match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset
+         * +/-hhmm
+         */
+        Pattern p = Pattern
+                .compile("(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(([+-])(\\d{2})(\\d{2}))?");
+        Matcher m = p.matcher(time);
+
+        if (m.matches()) {
+
+            /*
+             * matched groups are numberes as follows: YYYY MM DD T HH MM SS +
+             * hh mm ^^^^ ^^ ^^ ^^ ^^ ^^ ^ ^^ ^^ 1 2 3 4 5 6 8 9 10 all groups
+             * are guaranteed to be numeric so conversion will always succeed
+             * (except group 8 which is either + or -)
+             */
+
+            Calendar cal = Calendar.getInstance();
+            cal.set(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) - 1,
+                    Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)),
+                    Integer.parseInt(m.group(5)), Integer.parseInt(m.group(6)));
+
+            /*
+             * if 7th group is matched then we have UTC offset information
+             * included
+             */
+            if (m.group(7) != null) {
+                int ohh = Integer.parseInt(m.group(9));
+                int omm = Integer.parseInt(m.group(10));
+
+                /* time zone offset is specified in miliseconds */
+                int offset = (ohh * 60 + omm) * 60 * 1000;
+
+                if (m.group(8).equals("-")) {
+                    offset = -offset;
+                }
+
+                TimeZone tz = TimeZone.getTimeZone("UTC");
+                tz.setRawOffset(offset);
+
+                cal.setTimeZone(tz);
+            }
+
+            mDate = cal.getTime();
+        }
+    }
+
+    public ObexTime(Date date) {
+        mDate = date;
+    }
+
+    public Date getTime() {
+        return mDate;
+    }
+
+    @Override
+    public String toString() {
+        if (mDate == null) {
+            return null;
+        }
+
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(mDate);
+
+        /* note that months are numbered stating from 0 */
+        return String.format(Locale.US, "%04d%02d%02dT%02d%02d%02d",
+                cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1,
+                cal.get(Calendar.DATE), cal.get(Calendar.HOUR_OF_DAY),
+                cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND));
+    }
+}