Merge "MAP: Support MAP Client role on Bluedroid."
diff --git a/Android.mk b/Android.mk
index 7ae59ea..33c1115 100644
--- a/Android.mk
+++ b/Android.mk
@@ -2,9 +2,13 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := optional
+src_dirs:= src/org/codeaurora/bluetooth/btcservice \
+           src/org/codeaurora/bluetooth/map \
+           src/org/codeaurora/bluetooth/ftp \
+           src/org/codeaurora/bluetooth/sap
 
 LOCAL_SRC_FILES := \
-        $(call all-java-files-under, src)
+        $(call all-java-files-under, $(src_dirs))
 
 LOCAL_PACKAGE_NAME := BluetoothExt
 LOCAL_CERTIFICATE := platform
@@ -19,4 +23,18 @@
 
 include $(BUILD_PACKAGE)
 
+include $(CLEAR_VARS)
+
+src_dirs:=  src/org/codeaurora/bluetooth/mapclient
+
+LOCAL_SRC_FILES := \
+        $(call all-java-files-under, $(src_dirs))
+
+LOCAL_MODULE:= org.codeaurora.bluetooth.mapclient
+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/mapclient/BluetoothMapBmessage.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapBmessage.java
new file mode 100644
index 0000000..eae2636
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapBmessage.java
@@ -0,0 +1,181 @@
+/*
+ * 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.mapclient;
+
+import com.android.vcard.VCardEntry;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+/**
+ * Object representation of message in bMessage format
+ * <p>
+ * This object will be received in {@link BluetoothMasClient#EVENT_GET_MESSAGE}
+ * callback message.
+ */
+public class BluetoothMapBmessage {
+
+    String mBmsgVersion;
+    Status mBmsgStatus;
+    Type mBmsgType;
+    String mBmsgFolder;
+
+    String mBbodyEncoding;
+    String mBbodyCharset;
+    String mBbodyLanguage;
+    int mBbodyLength;
+
+    String mMessage;
+
+    ArrayList<VCardEntry> mOriginators;
+    ArrayList<VCardEntry> mRecipients;
+
+    public enum Status {
+        READ, UNREAD
+    }
+
+    public enum Type {
+        EMAIL, SMS_GSM, SMS_CDMA, MMS
+    }
+
+    /**
+     * Constructs empty message object
+     */
+    public BluetoothMapBmessage() {
+        mOriginators = new ArrayList<VCardEntry>();
+        mRecipients = new ArrayList<VCardEntry>();
+    }
+
+    public VCardEntry getOriginator() {
+        if (mOriginators.size() > 0) {
+            return mOriginators.get(0);
+        } else {
+            return null;
+        }
+    }
+
+    public ArrayList<VCardEntry> getOriginators() {
+        return mOriginators;
+    }
+
+    public BluetoothMapBmessage addOriginator(VCardEntry vcard) {
+        mOriginators.add(vcard);
+        return this;
+    }
+
+    public ArrayList<VCardEntry> getRecipients() {
+        return mRecipients;
+    }
+
+    public BluetoothMapBmessage addRecipient(VCardEntry vcard) {
+        mRecipients.add(vcard);
+        return this;
+    }
+
+    public Status getStatus() {
+        return mBmsgStatus;
+    }
+
+    public BluetoothMapBmessage setStatus(Status status) {
+        mBmsgStatus = status;
+        return this;
+    }
+
+    public Type getType() {
+        return mBmsgType;
+    }
+
+    public BluetoothMapBmessage setType(Type type) {
+        mBmsgType = type;
+        return this;
+    }
+
+    public String getFolder() {
+        return mBmsgFolder;
+    }
+
+    public BluetoothMapBmessage setFolder(String folder) {
+        mBmsgFolder = folder;
+        return this;
+    }
+
+    public String getEncoding() {
+        return mBbodyEncoding;
+    }
+
+    public BluetoothMapBmessage setEncoding(String encoding) {
+        mBbodyEncoding = encoding;
+        return this;
+    }
+
+    public String getCharset() {
+        return mBbodyCharset;
+    }
+
+    public BluetoothMapBmessage setCharset(String charset) {
+        mBbodyCharset = charset;
+        return this;
+    }
+
+    public String getLanguage() {
+        return mBbodyLanguage;
+    }
+
+    public BluetoothMapBmessage setLanguage(String language) {
+        mBbodyLanguage = language;
+        return this;
+    }
+
+    public String getBodyContent() {
+        return mMessage;
+    }
+
+    public BluetoothMapBmessage setBodyContent(String body) {
+        mMessage = body;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        JSONObject json = new JSONObject();
+
+        try {
+            json.put("status", mBmsgStatus);
+            json.put("type", mBmsgType);
+            json.put("folder", mBmsgFolder);
+            json.put("message", mMessage);
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        return json.toString();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMapBmessageBuilder.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapBmessageBuilder.java
new file mode 100644
index 0000000..b8683d0
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapBmessageBuilder.java
@@ -0,0 +1,173 @@
+/*
+ * 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.mapclient;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntry.EmailData;
+import com.android.vcard.VCardEntry.NameData;
+import com.android.vcard.VCardEntry.PhoneData;
+
+import java.util.List;
+
+class BluetoothMapBmessageBuilder {
+
+    private final static String CRLF = "\r\n";
+
+    private final static String BMSG_BEGIN = "BEGIN:BMSG";
+    private final static String BMSG_VERSION = "VERSION:1.0";
+    private final static String BMSG_STATUS = "STATUS:";
+    private final static String BMSG_TYPE = "TYPE:";
+    private final static String BMSG_FOLDER = "FOLDER:";
+    private final static String BMSG_END = "END:BMSG";
+
+    private final static String BENV_BEGIN = "BEGIN:BENV";
+    private final static String BENV_END = "END:BENV";
+
+    private final static String BBODY_BEGIN = "BEGIN:BBODY";
+    private final static String BBODY_ENCODING = "ENCODING:";
+    private final static String BBODY_CHARSET = "CHARSET:";
+    private final static String BBODY_LANGUAGE = "LANGUAGE:";
+    private final static String BBODY_LENGTH = "LENGTH:";
+    private final static String BBODY_END = "END:BBODY";
+
+    private final static String MSG_BEGIN = "BEGIN:MSG";
+    private final static String MSG_END = "END:MSG";
+
+    private final static String VCARD_BEGIN = "BEGIN:VCARD";
+    private final static String VCARD_VERSION = "VERSION:2.1";
+    private final static String VCARD_N = "N:";
+    private final static String VCARD_EMAIL = "EMAIL:";
+    private final static String VCARD_TEL = "TEL:";
+    private final static String VCARD_END = "END:VCARD";
+
+    private final StringBuilder mBmsg;
+
+    private BluetoothMapBmessageBuilder() {
+        mBmsg = new StringBuilder();
+    }
+
+    static public String createBmessage(BluetoothMapBmessage bmsg) {
+        BluetoothMapBmessageBuilder b = new BluetoothMapBmessageBuilder();
+
+        b.build(bmsg);
+
+        return b.mBmsg.toString();
+    }
+
+    private void build(BluetoothMapBmessage bmsg) {
+        int bodyLen = MSG_BEGIN.length() + MSG_END.length() + 3 * CRLF.length()
+                + bmsg.mMessage.getBytes().length;
+
+        mBmsg.append(BMSG_BEGIN).append(CRLF);
+
+        mBmsg.append(BMSG_VERSION).append(CRLF);
+        mBmsg.append(BMSG_STATUS).append(bmsg.mBmsgStatus).append(CRLF);
+        mBmsg.append(BMSG_TYPE).append(bmsg.mBmsgType).append(CRLF);
+        mBmsg.append(BMSG_FOLDER).append(bmsg.mBmsgFolder).append(CRLF);
+
+        for (VCardEntry vcard : bmsg.mOriginators) {
+            buildVcard(vcard);
+        }
+
+        {
+            mBmsg.append(BENV_BEGIN).append(CRLF);
+
+            for (VCardEntry vcard : bmsg.mRecipients) {
+                buildVcard(vcard);
+            }
+
+            {
+                mBmsg.append(BBODY_BEGIN).append(CRLF);
+
+                if (bmsg.mBbodyEncoding != null) {
+                    mBmsg.append(BBODY_ENCODING).append(bmsg.mBbodyEncoding).append(CRLF);
+                }
+
+                if (bmsg.mBbodyCharset != null) {
+                    mBmsg.append(BBODY_CHARSET).append(bmsg.mBbodyCharset).append(CRLF);
+                }
+
+                if (bmsg.mBbodyLanguage != null) {
+                    mBmsg.append(BBODY_LANGUAGE).append(bmsg.mBbodyLanguage).append(CRLF);
+                }
+
+                mBmsg.append(BBODY_LENGTH).append(bodyLen).append(CRLF);
+
+                {
+                    mBmsg.append(MSG_BEGIN).append(CRLF);
+
+                    mBmsg.append(bmsg.mMessage).append(CRLF);
+
+                    mBmsg.append(MSG_END).append(CRLF);
+                }
+
+                mBmsg.append(BBODY_END).append(CRLF);
+            }
+
+            mBmsg.append(BENV_END).append(CRLF);
+        }
+
+        mBmsg.append(BMSG_END).append(CRLF);
+    }
+
+    private void buildVcard(VCardEntry vcard) {
+        String n = buildVcardN(vcard);
+        List<PhoneData> tel = vcard.getPhoneList();
+        List<EmailData> email = vcard.getEmailList();
+
+        mBmsg.append(VCARD_BEGIN).append(CRLF);
+
+        mBmsg.append(VCARD_VERSION).append(CRLF);
+
+        mBmsg.append(VCARD_N).append(n).append(CRLF);
+
+        if (tel != null && tel.size() > 0) {
+            mBmsg.append(VCARD_TEL).append(tel.get(0).getNumber()).append(CRLF);
+        }
+
+        if (email != null && email.size() > 0) {
+            mBmsg.append(VCARD_EMAIL).append(email.get(0).getAddress()).append(CRLF);
+        }
+
+        mBmsg.append(VCARD_END).append(CRLF);
+    }
+
+    private String buildVcardN(VCardEntry vcard) {
+        NameData nd = vcard.getNameData();
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(nd.getFamily()).append(";");
+        sb.append(nd.getGiven() == null ? "" : nd.getGiven()).append(";");
+        sb.append(nd.getMiddle() == null ? "" : nd.getMiddle()).append(";");
+        sb.append(nd.getPrefix() == null ? "" : nd.getPrefix()).append(";");
+        sb.append(nd.getSuffix() == null ? "" : nd.getSuffix());
+
+        return sb.toString();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMapBmessageParser.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapBmessageParser.java
new file mode 100644
index 0000000..2081ba8
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapBmessageParser.java
@@ -0,0 +1,433 @@
+/*
+ * 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.mapclient;
+
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryConstructor;
+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 com.android.vcard.exception.VCardVersionException;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapBmessage.Status;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapBmessage.Type;
+import org.codeaurora.bluetooth.utils.BmsgTokenizer;
+import org.codeaurora.bluetooth.utils.BmsgTokenizer.Property;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.text.ParseException;
+
+class BluetoothMapBmessageParser {
+
+    private final static String TAG = "BluetoothMapBmessageParser";
+
+    private final static String CRLF = "\r\n";
+
+    private final static Property BEGIN_BMSG = new Property("BEGIN", "BMSG");
+    private final static Property END_BMSG = new Property("END", "BMSG");
+
+    private final static Property BEGIN_VCARD = new Property("BEGIN", "VCARD");
+    private final static Property END_VCARD = new Property("END", "VCARD");
+
+    private final static Property BEGIN_BENV = new Property("BEGIN", "BENV");
+    private final static Property END_BENV = new Property("END", "BENV");
+
+    private final static Property BEGIN_BBODY = new Property("BEGIN", "BBODY");
+    private final static Property END_BBODY = new Property("END", "BBODY");
+
+    private final static Property BEGIN_MSG = new Property("BEGIN", "MSG");
+    private final static Property END_MSG = new Property("END", "MSG");
+
+    private final static int CRLF_LEN = 2;
+
+    /*
+     * length of "container" for 'message' in bmessage-body-content:
+     * BEGIN:MSG<CRLF> + <CRLF> + END:MSG<CRFL>
+     */
+    private final static int MSG_CONTAINER_LEN = 22;
+
+    private BmsgTokenizer mParser;
+
+    private final BluetoothMapBmessage mBmsg;
+
+    private BluetoothMapBmessageParser() {
+        mBmsg = new BluetoothMapBmessage();
+    }
+
+    static public BluetoothMapBmessage createBmessage(String str) {
+        BluetoothMapBmessageParser p = new BluetoothMapBmessageParser();
+
+        try {
+            p.parse(str);
+        } catch (IOException e) {
+            Log.e(TAG, "I/O exception when parsing bMessage", e);
+            return null;
+        } catch (ParseException e) {
+            Log.e(TAG, "Cannot parse bMessage", e);
+            return null;
+        }
+
+        return p.mBmsg;
+    }
+
+    private ParseException expected(Property... props) {
+        boolean first = true;
+        StringBuilder sb = new StringBuilder();
+
+        for (Property prop : props) {
+            if (!first) {
+                sb.append(" or ");
+            }
+            sb.append(prop);
+            first = false;
+        }
+
+        return new ParseException("Expected: " + sb.toString(), mParser.pos());
+    }
+
+    private void parse(String str) throws IOException, ParseException {
+
+        Property prop;
+
+        /*
+         * <bmessage-object>::= { "BEGIN:BMSG" <CRLF> <bmessage-property>
+         * [<bmessage-originator>]* <bmessage-envelope> "END:BMSG" <CRLF> }
+         */
+
+        mParser = new BmsgTokenizer(str + CRLF);
+
+        prop = mParser.next();
+        if (!prop.equals(BEGIN_BMSG)) {
+            throw expected(BEGIN_BMSG);
+        }
+
+        prop = parseProperties();
+
+        while (prop.equals(BEGIN_VCARD)) {
+
+            /* <bmessage-originator>::= <vcard> <CRLF> */
+
+            StringBuilder vcard = new StringBuilder();
+            prop = extractVcard(vcard);
+
+            VCardEntry entry = parseVcard(vcard.toString());
+            mBmsg.mOriginators.add(entry);
+        }
+
+        if (!prop.equals(BEGIN_BENV)) {
+            throw expected(BEGIN_BENV);
+        }
+
+        prop = parseEnvelope(1);
+
+        if (!prop.equals(END_BMSG)) {
+            throw expected(END_BENV);
+        }
+
+        /*
+         * there should be no meaningful data left in stream here so we just
+         * ignore whatever is left
+         */
+
+        mParser = null;
+    }
+
+    private Property parseProperties() throws ParseException {
+
+        Property prop;
+
+        /*
+         * <bmessage-property>::=<bmessage-version-property>
+         * <bmessage-readstatus-property> <bmessage-type-property>
+         * <bmessage-folder-property> <bmessage-version-property>::="VERSION:"
+         * <common-digit>*"."<common-digit>* <CRLF>
+         * <bmessage-readstatus-property>::="STATUS:" 'readstatus' <CRLF>
+         * <bmessage-type-property>::="TYPE:" 'type' <CRLF>
+         * <bmessage-folder-property>::="FOLDER:" 'foldername' <CRLF>
+         */
+
+        do {
+            prop = mParser.next();
+
+            if (prop.name.equals("VERSION")) {
+                mBmsg.mBmsgVersion = prop.value;
+
+            } else if (prop.name.equals("STATUS")) {
+                for (Status s : Status.values()) {
+                    if (prop.value.equals(s.toString())) {
+                        mBmsg.mBmsgStatus = s;
+                        break;
+                    }
+                }
+
+            } else if (prop.name.equals("TYPE")) {
+                for (Type t : Type.values()) {
+                    if (prop.value.equals(t.toString())) {
+                        mBmsg.mBmsgType = t;
+                        break;
+                    }
+                }
+
+            } else if (prop.name.equals("FOLDER")) {
+                mBmsg.mBmsgFolder = prop.value;
+
+            }
+
+        } while (!prop.equals(BEGIN_VCARD) && !prop.equals(BEGIN_BENV));
+
+        return prop;
+    }
+
+    private Property parseEnvelope(int level) throws IOException, ParseException {
+
+        Property prop;
+
+        /*
+         * we can support as many nesting level as we want, but MAP spec clearly
+         * defines that there should be no more than 3 levels. so we verify it
+         * here.
+         */
+
+        if (level > 3) {
+            throw new ParseException("bEnvelope is nested more than 3 times", mParser.pos());
+        }
+
+        /*
+         * <bmessage-envelope> ::= { "BEGIN:BENV" <CRLF> [<bmessage-recipient>]*
+         * <bmessage-envelope> | <bmessage-content> "END:BENV" <CRLF> }
+         */
+
+        prop = mParser.next();
+
+        while (prop.equals(BEGIN_VCARD)) {
+
+            /* <bmessage-originator>::= <vcard> <CRLF> */
+
+            StringBuilder vcard = new StringBuilder();
+            prop = extractVcard(vcard);
+
+            if (level == 1) {
+                VCardEntry entry = parseVcard(vcard.toString());
+                mBmsg.mRecipients.add(entry);
+            }
+        }
+
+        if (prop.equals(BEGIN_BENV)) {
+            prop = parseEnvelope(level + 1);
+
+        } else if (prop.equals(BEGIN_BBODY)) {
+            prop = parseBody();
+
+        } else {
+            throw expected(BEGIN_BENV, BEGIN_BBODY);
+        }
+
+        if (!prop.equals(END_BENV)) {
+            throw expected(END_BENV);
+        }
+
+        return mParser.next();
+    }
+
+    private Property parseBody() throws IOException, ParseException {
+
+        Property prop;
+
+        /*
+         * <bmessage-content>::= { "BEGIN:BBODY"<CRLF> [<bmessage-body-part-ID>
+         * <CRLF>] <bmessage-body-property> <bmessage-body-content>* <CRLF>
+         * "END:BBODY"<CRLF> } <bmessage-body-part-ID>::="PARTID:" 'Part-ID'
+         * <bmessage-body-property>::=[<bmessage-body-encoding-property>]
+         * [<bmessage-body-charset-property>]
+         * [<bmessage-body-language-property>]
+         * <bmessage-body-content-length-property>
+         * <bmessage-body-encoding-property>::="ENCODING:"'encoding' <CRLF>
+         * <bmessage-body-charset-property>::="CHARSET:"'charset' <CRLF>
+         * <bmessage-body-language-property>::="LANGUAGE:"'language' <CRLF>
+         * <bmessage-body-content-length-property>::= "LENGTH:" <common-digit>*
+         * <CRLF>
+         */
+
+        do {
+            prop = mParser.next();
+
+            if (prop.name.equals("PARTID")) {
+            } else if (prop.name.equals("ENCODING")) {
+                mBmsg.mBbodyEncoding = prop.value;
+
+            } else if (prop.name.equals("CHARSET")) {
+                mBmsg.mBbodyCharset = prop.value;
+
+            } else if (prop.name.equals("LANGUAGE")) {
+                mBmsg.mBbodyLanguage = prop.value;
+
+            } else if (prop.name.equals("LENGTH")) {
+                try {
+                    mBmsg.mBbodyLength = Integer.valueOf(prop.value);
+                } catch (NumberFormatException e) {
+                    throw new ParseException("Invalid LENGTH value", mParser.pos());
+                }
+
+            }
+
+        } while (!prop.equals(BEGIN_MSG));
+
+        /*
+         * <bmessage-body-content>::={ "BEGIN:MSG"<CRLF> 'message'<CRLF>
+         * "END:MSG"<CRLF> }
+         */
+
+        int messageLen = mBmsg.mBbodyLength - MSG_CONTAINER_LEN;
+        int offset = messageLen + CRLF_LEN;
+        int restartPos = mParser.pos() + offset;
+
+        /*
+         * length is specified in bytes so we need to convert from unicode
+         * string back to bytes array
+         */
+
+        String remng = mParser.remaining();
+        byte[] data = remng.getBytes();
+
+        /* restart parsing from after 'message'<CRLF> */
+        mParser = new BmsgTokenizer(new String(data, offset, data.length - offset), restartPos);
+
+        prop = mParser.next(true);
+
+        if (prop != null && prop.equals(END_MSG)) {
+            mBmsg.mMessage = new String(data, 0, messageLen);
+        } else {
+
+            data = null;
+
+            /*
+             * now we check if bMessage can be parsed if LENGTH is handled as
+             * number of characters instead of number of bytes
+             */
+
+            Log.w(TAG, "byte LENGTH seems to be invalid, trying with char length");
+
+            mParser = new BmsgTokenizer(remng.substring(offset));
+
+            prop = mParser.next();
+
+            if (!prop.equals(END_MSG)) {
+                throw expected(END_MSG);
+            }
+
+            mBmsg.mMessage = remng.substring(0, messageLen);
+        }
+
+        prop = mParser.next();
+
+        if (!prop.equals(END_BBODY)) {
+            throw expected(END_BBODY);
+        }
+
+        return mParser.next();
+    }
+
+    private Property extractVcard(StringBuilder out) throws IOException, ParseException {
+        Property prop;
+
+        out.append(BEGIN_VCARD).append(CRLF);
+
+        do {
+            prop = mParser.next();
+            out.append(prop).append(CRLF);
+        } while (!prop.equals(END_VCARD));
+
+        return mParser.next();
+    }
+
+    private class VcardHandler implements VCardEntryHandler {
+
+        VCardEntry vcard;
+
+        @Override
+        public void onStart() {
+        }
+
+        @Override
+        public void onEntryCreated(VCardEntry entry) {
+            vcard = entry;
+        }
+
+        @Override
+        public void onEnd() {
+        }
+    };
+
+    private VCardEntry parseVcard(String str) throws IOException, ParseException {
+        VCardEntry vcard = null;
+
+        try {
+            VCardParser p = new VCardParser_V21();
+            VCardEntryConstructor c = new VCardEntryConstructor();
+            VcardHandler handler = new VcardHandler();
+            c.addEntryHandler(handler);
+            p.addInterpreter(c);
+            p.parse(new ByteArrayInputStream(str.getBytes()));
+
+            vcard = handler.vcard;
+
+        } catch (VCardVersionException e1) {
+
+            try {
+                VCardParser p = new VCardParser_V30();
+                VCardEntryConstructor c = new VCardEntryConstructor();
+                VcardHandler handler = new VcardHandler();
+                c.addEntryHandler(handler);
+                p.addInterpreter(c);
+                p.parse(new ByteArrayInputStream(str.getBytes()));
+
+                vcard = handler.vcard;
+
+            } catch (VCardVersionException e2) {
+                // will throw below
+            } catch (VCardException e2) {
+                // will throw below
+            }
+
+        } catch (VCardException e1) {
+            // will throw below
+        }
+
+        if (vcard == null) {
+            throw new ParseException("Cannot parse vCard object (neither 2.1 nor 3.0?)",
+                    mParser.pos());
+        }
+
+        return vcard;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMapEventReport.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapEventReport.java
new file mode 100644
index 0000000..bcc94b0
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapEventReport.java
@@ -0,0 +1,236 @@
+/*
+ * 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.mapclient;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.HashMap;
+
+/**
+ * Object representation of event report received by MNS
+ * <p>
+ * This object will be received in {@link BluetoothMasClient#EVENT_EVENT_REPORT}
+ * callback message.
+ */
+public class BluetoothMapEventReport {
+
+    private final static String TAG = "BluetoothMapEventReport";
+
+    public enum Type {
+        NEW_MESSAGE("NewMessage"), DELIVERY_SUCCESS("DeliverySuccess"),
+        SENDING_SUCCESS("SendingSuccess"), DELIVERY_FAILURE("DeliveryFailure"),
+        SENDING_FAILURE("SendingFailure"), MEMORY_FULL("MemoryFull"),
+        MEMORY_AVAILABLE("MemoryAvailable"), MESSAGE_DELETED("MessageDeleted"),
+        MESSAGE_SHIFT("MessageShift");
+
+        private final String mSpecName;
+
+        private Type(String specName) {
+            mSpecName = specName;
+        }
+
+        @Override
+        public String toString() {
+            return mSpecName;
+        }
+    }
+
+    private final Type mType;
+
+    private final String mHandle;
+
+    private final String mFolder;
+
+    private final String mOldFolder;
+
+    private final BluetoothMapBmessage.Type mMsgType;
+
+    private BluetoothMapEventReport(HashMap<String, String> attrs) throws IllegalArgumentException {
+        mType = parseType(attrs.get("type"));
+
+        if (mType != Type.MEMORY_FULL && mType != Type.MEMORY_AVAILABLE) {
+            String handle = attrs.get("handle");
+            try {
+                /* just to validate */
+                new BigInteger(attrs.get("handle"), 16);
+
+                mHandle = attrs.get("handle");
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Invalid value for handle:" + handle);
+            }
+        } else {
+            mHandle = null;
+        }
+
+        mFolder = attrs.get("folder");
+
+        mOldFolder = attrs.get("old_folder");
+
+        if (mType != Type.MEMORY_FULL && mType != Type.MEMORY_AVAILABLE) {
+            String s = attrs.get("msg_type");
+
+            if ("".equals(s)) {
+                // Some phones (e.g. SGS3 for MessageDeleted) send empty
+                // msg_type, in such case leave it as null rather than throw
+                // parse exception
+                mMsgType = null;
+            } else {
+                mMsgType = parseMsgType(s);
+            }
+        } else {
+            mMsgType = null;
+        }
+    }
+
+    private Type parseType(String type) throws IllegalArgumentException {
+        for (Type t : Type.values()) {
+            if (t.toString().equals(type)) {
+                return t;
+            }
+        }
+
+        throw new IllegalArgumentException("Invalid value for type: " + type);
+    }
+
+    private BluetoothMapBmessage.Type parseMsgType(String msgType) throws IllegalArgumentException {
+        for (BluetoothMapBmessage.Type t : BluetoothMapBmessage.Type.values()) {
+            if (t.name().equals(msgType)) {
+                return t;
+            }
+        }
+
+        throw new IllegalArgumentException("Invalid value for msg_type: " + msgType);
+    }
+
+    /**
+     * @return {@link BluetoothMapEventReport.Type} object corresponding to
+     *         <code>type</code> application parameter in MAP specification
+     */
+    public Type getType() {
+        return mType;
+    }
+
+    /**
+     * @return value corresponding to <code>handle</code> parameter in MAP
+     *         specification
+     */
+    public String getHandle() {
+        return mHandle;
+    }
+
+    /**
+     * @return value corresponding to <code>folder</code> parameter in MAP
+     *         specification
+     */
+    public String getFolder() {
+        return mFolder;
+    }
+
+    /**
+     * @return value corresponding to <code>old_folder</code> parameter in MAP
+     *         specification
+     */
+    public String getOldFolder() {
+        return mOldFolder;
+    }
+
+    /**
+     * @return {@link BluetoothMapBmessage.Type} object corresponding to
+     *         <code>msg_type</code> application parameter in MAP specification
+     */
+    public BluetoothMapBmessage.Type getMsgType() {
+        return mMsgType;
+    }
+
+    @Override
+    public String toString() {
+        JSONObject json = new JSONObject();
+
+        try {
+            json.put("type", mType);
+            json.put("handle", mHandle);
+            json.put("folder", mFolder);
+            json.put("old_folder", mOldFolder);
+            json.put("msg_type", mMsgType);
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        return json.toString();
+    }
+
+    static BluetoothMapEventReport fromStream(DataInputStream in) {
+        BluetoothMapEventReport ev = null;
+
+        try {
+            XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
+            xpp.setInput(in, "utf-8");
+
+            int event = xpp.getEventType();
+            while (event != XmlPullParser.END_DOCUMENT) {
+                switch (event) {
+                    case XmlPullParser.START_TAG:
+                        if (xpp.getName().equals("event")) {
+                            HashMap<String, String> attrs = new HashMap<String, String>();
+
+                            for (int i = 0; i < xpp.getAttributeCount(); i++) {
+                                attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i));
+                            }
+
+                            ev = new BluetoothMapEventReport(attrs);
+
+                            // return immediately, only one event should be here
+                            return ev;
+                        }
+                        break;
+                }
+
+                event = xpp.next();
+            }
+
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "XML parser error when parsing XML", e);
+        } catch (IOException e) {
+            Log.e(TAG, "I/O error when parsing XML", e);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Invalid event received", e);
+        }
+
+        return ev;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMapFolderListing.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapFolderListing.java
new file mode 100644
index 0000000..c2d531a
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapFolderListing.java
@@ -0,0 +1,81 @@
+/*
+ * 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.mapclient;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+class BluetoothMapFolderListing {
+
+    private static final String TAG = "BluetoothMasFolderListing";
+
+    private final ArrayList<String> mFolders;
+
+    public BluetoothMapFolderListing(InputStream in) {
+        mFolders = new ArrayList<String>();
+
+        parse(in);
+    }
+
+    public void parse(InputStream in) {
+
+        try {
+            XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
+            xpp.setInput(in, "utf-8");
+
+            int event = xpp.getEventType();
+            while (event != XmlPullParser.END_DOCUMENT) {
+                switch (event) {
+                    case XmlPullParser.START_TAG:
+                        if (xpp.getName().equals("folder")) {
+                            mFolders.add(xpp.getAttributeValue(null, "name"));
+                        }
+                        break;
+                }
+
+                event = xpp.next();
+            }
+
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "XML parser error when parsing XML", e);
+        } catch (IOException e) {
+            Log.e(TAG, "I/O error when parsing XML", e);
+        }
+    }
+
+    public ArrayList<String> getList() {
+        return mFolders;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMapMessage.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapMessage.java
new file mode 100644
index 0000000..05b2dd7
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapMessage.java
@@ -0,0 +1,345 @@
+/*
+ * 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.mapclient;
+import org.codeaurora.bluetooth.utils.ObexTime;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.HashMap;
+
+/**
+ * Object representation of message received in messages listing
+ * <p>
+ * This object will be received in
+ * {@link BluetoothMasClient#EVENT_GET_MESSAGES_LISTING} callback message.
+ */
+public class BluetoothMapMessage {
+
+    private final String mHandle;
+
+    private final String mSubject;
+
+    private final Date mDateTime;
+
+    private final String mSenderName;
+
+    private final String mSenderAddressing;
+
+    private final String mReplytoAddressing;
+
+    private final String mRecipientName;
+
+    private final String mRecipientAddressing;
+
+    private final Type mType;
+
+    private final int mSize;
+
+    private final boolean mText;
+
+    private final ReceptionStatus mReceptionStatus;
+
+    private final int mAttachmentSize;
+
+    private final boolean mPriority;
+
+    private final boolean mRead;
+
+    private final boolean mSent;
+
+    private final boolean mProtected;
+
+    public enum Type {
+        UNKNOWN, EMAIL, SMS_GSM, SMS_CDMA, MMS
+    };
+
+    public enum ReceptionStatus {
+        UNKNOWN, COMPLETE, FRACTIONED, NOTIFICATION
+    }
+
+    BluetoothMapMessage(HashMap<String, String> attrs) throws IllegalArgumentException {
+        int size;
+
+        try {
+            /* just to validate */
+            new BigInteger(attrs.get("handle"), 16);
+
+            mHandle = attrs.get("handle");
+        } catch (NumberFormatException e) {
+            /*
+             * handle MUST have proper value, if it does not then throw
+             * something here
+             */
+            throw new IllegalArgumentException(e);
+        }
+
+        mSubject = attrs.get("subject");
+
+        mDateTime = (new ObexTime(attrs.get("datetime"))).getTime();
+
+        mSenderName = attrs.get("sender_name");
+
+        mSenderAddressing = attrs.get("sender_addressing");
+
+        mReplytoAddressing = attrs.get("replyto_addressing");
+
+        mRecipientName = attrs.get("recipient_name");
+
+        mRecipientAddressing = attrs.get("recipient_addressing");
+
+        mType = strToType(attrs.get("type"));
+
+        try {
+            size = Integer.parseInt(attrs.get("size"));
+        } catch (NumberFormatException e) {
+            size = 0;
+        }
+
+        mSize = size;
+
+        mText = yesnoToBoolean(attrs.get("text"));
+
+        mReceptionStatus = strToReceptionStatus(attrs.get("reception_status"));
+
+        try {
+            size = Integer.parseInt(attrs.get("attachment_size"));
+        } catch (NumberFormatException e) {
+            size = 0;
+        }
+
+        mAttachmentSize = size;
+
+        mPriority = yesnoToBoolean(attrs.get("priority"));
+
+        mRead = yesnoToBoolean(attrs.get("read"));
+
+        mSent = yesnoToBoolean(attrs.get("sent"));
+
+        mProtected = yesnoToBoolean(attrs.get("protected"));
+    }
+
+    private boolean yesnoToBoolean(String yesno) {
+        return "yes".equals(yesno);
+    }
+
+    private Type strToType(String s) {
+        if ("EMAIL".equals(s)) {
+            return Type.EMAIL;
+        } else if ("SMS_GSM".equals(s)) {
+            return Type.SMS_GSM;
+        } else if ("SMS_CDMA".equals(s)) {
+            return Type.SMS_CDMA;
+        } else if ("MMS".equals(s)) {
+            return Type.MMS;
+        }
+
+        return Type.UNKNOWN;
+    }
+
+    private ReceptionStatus strToReceptionStatus(String s) {
+        if ("complete".equals(s)) {
+            return ReceptionStatus.COMPLETE;
+        } else if ("fractioned".equals(s)) {
+            return ReceptionStatus.FRACTIONED;
+        } else if ("notification".equals(s)) {
+            return ReceptionStatus.NOTIFICATION;
+        }
+
+        return ReceptionStatus.UNKNOWN;
+    }
+
+    @Override
+    public String toString() {
+        JSONObject json = new JSONObject();
+
+        try {
+            json.put("handle", mHandle);
+            json.put("subject", mSubject);
+            json.put("datetime", mDateTime);
+            json.put("sender_name", mSenderName);
+            json.put("sender_addressing", mSenderAddressing);
+            json.put("replyto_addressing", mReplytoAddressing);
+            json.put("recipient_name", mRecipientName);
+            json.put("recipient_addressing", mRecipientAddressing);
+            json.put("type", mType);
+            json.put("size", mSize);
+            json.put("text", mText);
+            json.put("reception_status", mReceptionStatus);
+            json.put("attachment_size", mAttachmentSize);
+            json.put("priority", mPriority);
+            json.put("read", mRead);
+            json.put("sent", mSent);
+            json.put("protected", mProtected);
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        return json.toString();
+    }
+
+    /**
+     * @return value corresponding to <code>handle</code> parameter in MAP
+     *         specification
+     */
+    public String getHandle() {
+        return mHandle;
+    }
+
+    /**
+     * @return value corresponding to <code>subject</code> parameter in MAP
+     *         specification
+     */
+    public String getSubject() {
+        return mSubject;
+    }
+
+    /**
+     * @return <code>Date</code> object corresponding to <code>datetime</code>
+     *         parameter in MAP specification
+     */
+    public Date getDateTime() {
+        return mDateTime;
+    }
+
+    /**
+     * @return value corresponding to <code>sender_name</code> parameter in MAP
+     *         specification
+     */
+    public String getSenderName() {
+        return mSenderName;
+    }
+
+    /**
+     * @return value corresponding to <code>sender_addressing</code> parameter
+     *         in MAP specification
+     */
+    public String getSenderAddressing() {
+        return mSenderAddressing;
+    }
+
+    /**
+     * @return value corresponding to <code>replyto_addressing</code> parameter
+     *         in MAP specification
+     */
+    public String getReplytoAddressing() {
+        return mReplytoAddressing;
+    }
+
+    /**
+     * @return value corresponding to <code>recipient_name</code> parameter in
+     *         MAP specification
+     */
+    public String getRecipientName() {
+        return mRecipientName;
+    }
+
+    /**
+     * @return value corresponding to <code>recipient_addressing</code>
+     *         parameter in MAP specification
+     */
+    public String getRecipientAddressing() {
+        return mRecipientAddressing;
+    }
+
+    /**
+     * @return {@link Type} object corresponding to <code>type</code> parameter
+     *         in MAP specification
+     */
+    public Type getType() {
+        return mType;
+    }
+
+    /**
+     * @return value corresponding to <code>size</code> parameter in MAP
+     *         specification
+     */
+    public int getSize() {
+        return mSize;
+    }
+
+    /**
+     * @return {@link .ReceptionStatus} object corresponding to
+     *         <code>reception_status</code> parameter in MAP specification
+     */
+    public ReceptionStatus getReceptionStatus() {
+        return mReceptionStatus;
+    }
+
+    /**
+     * @return value corresponding to <code>attachment_size</code> parameter in
+     *         MAP specification
+     */
+    public int getAttachmentSize() {
+        return mAttachmentSize;
+    }
+
+    /**
+     * @return value corresponding to <code>text</code> parameter in MAP
+     *         specification
+     */
+    public boolean isText() {
+        return mText;
+    }
+
+    /**
+     * @return value corresponding to <code>priority</code> parameter in MAP
+     *         specification
+     */
+    public boolean isPriority() {
+        return mPriority;
+    }
+
+    /**
+     * @return value corresponding to <code>read</code> parameter in MAP
+     *         specification
+     */
+    public boolean isRead() {
+        return mRead;
+    }
+
+    /**
+     * @return value corresponding to <code>sent</code> parameter in MAP
+     *         specification
+     */
+    public boolean isSent() {
+        return mSent;
+    }
+
+    /**
+     * @return value corresponding to <code>protected</code> parameter in MAP
+     *         specification
+     */
+    public boolean isProtected() {
+        return mProtected;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMapMessagesListing.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapMessagesListing.java
new file mode 100644
index 0000000..e895dcc
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapMessagesListing.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.mapclient;
+
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+class BluetoothMapMessagesListing {
+
+    private static final String TAG = "BluetoothMapMessagesListing";
+
+    private final ArrayList<BluetoothMapMessage> mMessages;
+
+    public BluetoothMapMessagesListing(InputStream in) {
+        mMessages = new ArrayList<BluetoothMapMessage>();
+
+        parse(in);
+    }
+
+    public void parse(InputStream in) {
+
+        try {
+            XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
+            xpp.setInput(in, "utf-8");
+
+            int event = xpp.getEventType();
+            while (event != XmlPullParser.END_DOCUMENT) {
+                switch (event) {
+                    case XmlPullParser.START_TAG:
+                        if (xpp.getName().equals("msg")) {
+
+                            HashMap<String, String> attrs = new HashMap<String, String>();
+
+                            for (int i = 0; i < xpp.getAttributeCount(); i++) {
+                                attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i));
+                            }
+
+                            try {
+                                BluetoothMapMessage msg = new BluetoothMapMessage(attrs);
+                                mMessages.add(msg);
+                            } catch (IllegalArgumentException e) {
+                                /* TODO: provide something more useful here */
+                                Log.w(TAG, "Invalid <msg/>");
+                            }
+                        }
+                        break;
+                }
+
+                event = xpp.next();
+            }
+
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "XML parser error when parsing XML", e);
+        } catch (IOException e) {
+            Log.e(TAG, "I/O error when parsing XML", e);
+        }
+    }
+
+    public ArrayList<BluetoothMapMessage> getList() {
+        return mMessages;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMapRfcommTransport.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapRfcommTransport.java
new file mode 100644
index 0000000..8cc62a0
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMapRfcommTransport.java
@@ -0,0 +1,89 @@
+/*
+ * 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.mapclient;
+
+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 BluetoothMapRfcommTransport implements ObexTransport {
+    private final BluetoothSocket mSocket;
+
+    public BluetoothMapRfcommTransport(BluetoothSocket socket) {
+        super();
+        mSocket = socket;
+    }
+
+    @Override
+    public void create() throws IOException {
+    }
+
+    @Override
+    public void listen() throws IOException {
+    }
+
+    @Override
+    public void close() throws IOException {
+        mSocket.close();
+    }
+
+    @Override
+    public void connect() throws IOException {
+    }
+
+    @Override
+    public void disconnect() throws IOException {
+    }
+
+    @Override
+    public InputStream openInputStream() throws IOException {
+        return mSocket.getInputStream();
+    }
+
+    @Override
+    public OutputStream openOutputStream() throws IOException {
+        return mSocket.getOutputStream();
+    }
+
+    @Override
+    public DataInputStream openDataInputStream() throws IOException {
+        return new DataInputStream(openInputStream());
+    }
+
+    @Override
+    public DataOutputStream openDataOutputStream() throws IOException {
+        return new DataOutputStream(openOutputStream());
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasClient.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasClient.java
new file mode 100644
index 0000000..5d4ec2f
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasClient.java
@@ -0,0 +1,1114 @@
+/*
+ * 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.mapclient;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMasInstance;
+import android.bluetooth.BluetoothSocket;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import org.codeaurora.bluetooth.mapclient.BluetoothMasRequestSetMessageStatus.StatusIndicator;
+import org.codeaurora.bluetooth.utils.ObexTime;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.math.BigInteger;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+
+import javax.obex.ObexTransport;
+
+public class BluetoothMasClient {
+
+    private final static String TAG = "BluetoothMasClient";
+
+    private static final int SOCKET_CONNECTED = 10;
+
+    private static final int SOCKET_ERROR = 11;
+
+    /**
+     * Callback message sent when connection state changes
+     * <p>
+     * <code>arg1</code> is set to {@link #STATUS_OK} when connection is
+     * established successfully and {@link #STATUS_FAILED} when connection
+     * either failed or was disconnected (depends on request from application)
+     *
+     * @see #connect()
+     * @see #disconnect()
+     */
+    public static final int EVENT_CONNECT = 1;
+
+    /**
+     * Callback message sent when MSE accepted update inbox request
+     *
+     * @see #updateInbox()
+     */
+    public static final int EVENT_UPDATE_INBOX = 2;
+
+    /**
+     * Callback message sent when path is changed
+     * <p>
+     * <code>obj</code> is set to path currently set on MSE
+     *
+     * @see #setFolderRoot()
+     * @see #setFolderUp()
+     * @see #setFolderDown(String)
+     */
+    public static final int EVENT_SET_PATH = 3;
+
+    /**
+     * Callback message sent when folder listing is received
+     * <p>
+     * <code>obj</code> contains ArrayList of sub-folder names
+     *
+     * @see #getFolderListing()
+     * @see #getFolderListing(int, int)
+     */
+    public static final int EVENT_GET_FOLDER_LISTING = 4;
+
+    /**
+     * Callback message sent when folder listing size is received
+     * <p>
+     * <code>obj</code> contains number of items in folder listing
+     *
+     * @see #getFolderListingSize()
+     */
+    public static final int EVENT_GET_FOLDER_LISTING_SIZE = 5;
+
+    /**
+     * Callback message sent when messages listing is received
+     * <p>
+     * <code>obj</code> contains ArrayList of {@link BluetoothMapBmessage}
+     *
+     * @see #getMessagesListing(String, int)
+     * @see #getMessagesListing(String, int, MessagesFilter, int)
+     * @see #getMessagesListing(String, int, MessagesFilter, int, int, int)
+     */
+    public static final int EVENT_GET_MESSAGES_LISTING = 6;
+
+    /**
+     * Callback message sent when message is received
+     * <p>
+     * <code>obj</code> contains {@link BluetoothMapBmessage}
+     *
+     * @see #getMessage(String, CharsetType, boolean)
+     */
+    public static final int EVENT_GET_MESSAGE = 7;
+
+    /**
+     * Callback message sent when message status is changed
+     *
+     * @see #setMessageDeletedStatus(String, boolean)
+     * @see #setMessageReadStatus(String, boolean)
+     */
+    public static final int EVENT_SET_MESSAGE_STATUS = 8;
+
+    /**
+     * Callback message sent when message is pushed to MSE
+     * <p>
+     * <code>obj</code> contains handle of message as allocated by MSE
+     *
+     * @see #pushMessage(String, BluetoothMapBmessage, CharsetType)
+     * @see #pushMessage(String, BluetoothMapBmessage, CharsetType, boolean,
+     *      boolean)
+     */
+    public static final int EVENT_PUSH_MESSAGE = 9;
+
+    /**
+     * Callback message sent when notification status is changed
+     * <p>
+     * <code>obj</code> contains <code>1</code> if notifications are enabled and
+     * <code>0</code> otherwise
+     *
+     * @see #setNotificationRegistration(boolean)
+     */
+    public static final int EVENT_SET_NOTIFICATION_REGISTRATION = 10;
+
+    /**
+     * Callback message sent when event report is received from MSE to MNS
+     * <p>
+     * <code>obj</code> contains {@link BluetoothMapEventReport}
+     *
+     * @see #setNotificationRegistration(boolean)
+     */
+    public static final int EVENT_EVENT_REPORT = 11;
+
+    /**
+     * Callback message sent when messages listing size is received
+     * <p>
+     * <code>obj</code> contains number of items in messages listing
+     *
+     * @see #getMessagesListingSize()
+     */
+    public static final int EVENT_GET_MESSAGES_LISTING_SIZE = 12;
+
+    /**
+     * Status for callback message when request is successful
+     */
+    public static final int STATUS_OK = 0;
+
+    /**
+     * Status for callback message when request is not successful
+     */
+    public static final int STATUS_FAILED = 1;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_DEFAULT = 0x00000000;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_SUBJECT = 0x00000001;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_DATETIME = 0x00000002;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_SENDER_NAME = 0x00000004;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_SENDER_ADDRESSING = 0x00000008;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_RECIPIENT_NAME = 0x00000010;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_RECIPIENT_ADDRESSING = 0x00000020;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_TYPE = 0x00000040;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_SIZE = 0x00000080;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_RECEPTION_STATUS = 0x00000100;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_TEXT = 0x00000200;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_ATTACHMENT_SIZE = 0x00000400;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_PRIORITY = 0x00000800;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_READ = 0x00001000;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_SENT = 0x00002000;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_PROTECTED = 0x00004000;
+
+    /**
+     * Constant corresponding to <code>ParameterMask</code> application
+     * parameter value in MAP specification
+     */
+    public static final int PARAMETER_REPLYTO_ADDRESSING = 0x00008000;
+
+    public enum ConnectionState {
+        DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING;
+    }
+
+    public enum CharsetType {
+        NATIVE, UTF_8;
+    }
+
+    /** device associated with client */
+    private final BluetoothDevice mDevice;
+
+    /** MAS instance associated with client */
+    private final BluetoothMasInstance mMas;
+
+    /** callback handler to application */
+    private final Handler mCallback;
+
+    private ConnectionState mConnectionState = ConnectionState.DISCONNECTED;
+
+    private boolean mNotificationEnabled = false;
+
+    private SocketConnectThread mConnectThread = null;
+
+    private ObexTransport mObexTransport = null;
+
+    private BluetoothMasObexClientSession mObexSession = null;
+
+    private SessionHandler mSessionHandler = null;
+
+    private BluetoothMnsService mMnsService = null;
+
+    private ArrayDeque<String> mPath = null;
+
+    private static class SessionHandler extends Handler {
+
+        private final WeakReference<BluetoothMasClient> mClient;
+
+        public SessionHandler(BluetoothMasClient client) {
+            super();
+
+            mClient = new WeakReference<BluetoothMasClient>(client);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+
+            BluetoothMasClient client = mClient.get();
+            if (client == null) {
+                return;
+            }
+            Log.v(TAG, "handleMessage  "+msg.what);
+
+            switch (msg.what) {
+                case SOCKET_ERROR:
+                    client.mConnectThread = null;
+                    client.sendToClient(EVENT_CONNECT, false);
+                    break;
+
+                case SOCKET_CONNECTED:
+                    client.mConnectThread = null;
+
+                    client.mObexTransport = (ObexTransport) msg.obj;
+
+                    client.mObexSession = new BluetoothMasObexClientSession(client.mObexTransport,
+                            client.mSessionHandler);
+                    client.mObexSession.start();
+                    break;
+
+                case BluetoothMasObexClientSession.MSG_OBEX_CONNECTED:
+                    client.mPath.clear(); // we're in root after connected
+                    client.mConnectionState = ConnectionState.CONNECTED;
+                    client.sendToClient(EVENT_CONNECT, true);
+                    break;
+
+                case BluetoothMasObexClientSession.MSG_OBEX_DISCONNECTED:
+                    client.mConnectionState = ConnectionState.DISCONNECTED;
+                    client.mNotificationEnabled = false;
+                    client.mObexSession = null;
+                    client.sendToClient(EVENT_CONNECT, false);
+                    break;
+
+                case BluetoothMasObexClientSession.MSG_REQUEST_COMPLETED:
+                    BluetoothMasRequest request = (BluetoothMasRequest) msg.obj;
+                    int status = request.isSuccess() ? STATUS_OK : STATUS_FAILED;
+
+                    Log.v(TAG, "MSG_REQUEST_COMPLETED (" + status + ") for "
+                            + request.getClass().getName());
+
+                    if (request instanceof BluetoothMasRequestUpdateInbox) {
+                        client.sendToClient(EVENT_UPDATE_INBOX, request.isSuccess());
+
+                    } else if (request instanceof BluetoothMasRequestSetPath) {
+                        if (request.isSuccess()) {
+                            BluetoothMasRequestSetPath req = (BluetoothMasRequestSetPath) request;
+                            switch (req.mDir) {
+                                case UP:
+                                    if (client.mPath.size() > 0) {
+                                        client.mPath.removeLast();
+                                    }
+                                    break;
+
+                                case ROOT:
+                                    client.mPath.clear();
+                                    break;
+
+                                case DOWN:
+                                    client.mPath.addLast(req.mName);
+                                    break;
+                            }
+                        }
+
+                        client.sendToClient(EVENT_SET_PATH, request.isSuccess(),
+                                client.getCurrentPath());
+
+                    } else if (request instanceof BluetoothMasRequestGetFolderListing) {
+                        BluetoothMasRequestGetFolderListing req = (BluetoothMasRequestGetFolderListing) request;
+                        ArrayList<String> folders = req.getList();
+
+                        client.sendToClient(EVENT_GET_FOLDER_LISTING, request.isSuccess(), folders);
+
+                    } else if (request instanceof BluetoothMasRequestGetFolderListingSize) {
+                        int size = ((BluetoothMasRequestGetFolderListingSize) request).getSize();
+
+                        client.sendToClient(EVENT_GET_FOLDER_LISTING_SIZE, request.isSuccess(),
+                                size);
+
+                    } else if (request instanceof BluetoothMasRequestGetMessagesListing) {
+                        BluetoothMasRequestGetMessagesListing req = (BluetoothMasRequestGetMessagesListing) request;
+                        ArrayList<BluetoothMapMessage> msgs = req.getList();
+
+                        client.sendToClient(EVENT_GET_MESSAGES_LISTING, request.isSuccess(), msgs);
+
+                    } else if (request instanceof BluetoothMasRequestGetMessage) {
+                        BluetoothMasRequestGetMessage req = (BluetoothMasRequestGetMessage) request;
+                        BluetoothMapBmessage bmsg = req.getMessage();
+
+                        client.sendToClient(EVENT_GET_MESSAGE, request.isSuccess(), bmsg);
+
+                    } else if (request instanceof BluetoothMasRequestSetMessageStatus) {
+                        client.sendToClient(EVENT_SET_MESSAGE_STATUS, request.isSuccess());
+
+                    } else if (request instanceof BluetoothMasRequestPushMessage) {
+                        BluetoothMasRequestPushMessage req = (BluetoothMasRequestPushMessage) request;
+                        String handle = req.getMsgHandle();
+
+                        client.sendToClient(EVENT_PUSH_MESSAGE, request.isSuccess(), handle);
+
+                    } else if (request instanceof BluetoothMasRequestSetNotificationRegistration) {
+                        BluetoothMasRequestSetNotificationRegistration req = (BluetoothMasRequestSetNotificationRegistration) request;
+
+                        client.mNotificationEnabled = req.isSuccess() ? req.getStatus()
+                                : client.mNotificationEnabled;
+
+                        client.sendToClient(EVENT_SET_NOTIFICATION_REGISTRATION,
+                                request.isSuccess(),
+                                client.mNotificationEnabled ? 1 : 0);
+                    } else if (request instanceof BluetoothMasRequestGetMessagesListingSize) {
+                        int size = ((BluetoothMasRequestGetMessagesListingSize) request).getSize();
+                        client.sendToClient(EVENT_GET_MESSAGES_LISTING_SIZE, request.isSuccess(),
+                                size);
+                    }
+                    break;
+
+                case BluetoothMnsService.EVENT_REPORT:
+                    /* pass event report directly to app */
+                    client.sendToClient(EVENT_EVENT_REPORT, true, msg.obj);
+                    break;
+            }
+        }
+    }
+
+    private void sendToClient(int event, boolean success) {
+        sendToClient(event, success, null);
+    }
+
+    private void sendToClient(int event, boolean success, int param) {
+        sendToClient(event, success, Integer.valueOf(param));
+    }
+
+    private void sendToClient(int event, boolean success, Object param) {
+        if (success) {
+            mCallback.obtainMessage(event, STATUS_OK, mMas.getId(), param).sendToTarget();
+        } else {
+            mCallback.obtainMessage(event, STATUS_FAILED, mMas.getId(), null).sendToTarget();
+        }
+    }
+
+    private class SocketConnectThread extends Thread {
+        private BluetoothSocket socket = null;
+
+        public SocketConnectThread() {
+            super("SocketConnectThread");
+        }
+
+        @Override
+        public void run() {
+            try {
+                socket = mDevice.createRfcommSocket(mMas.getChannel());
+                socket.connect();
+
+                BluetoothMapRfcommTransport transport;
+                transport = new BluetoothMapRfcommTransport(socket);
+
+                mSessionHandler.obtainMessage(SOCKET_CONNECTED, transport).sendToTarget();
+            } catch (IOException e) {
+                Log.e(TAG, "Error when creating/connecting socket", e);
+
+                closeSocket();
+                mSessionHandler.obtainMessage(SOCKET_ERROR).sendToTarget();
+            }
+        }
+
+        @Override
+        public void interrupt() {
+            closeSocket();
+        }
+
+        private void closeSocket() {
+            try {
+                if (socket != null) {
+                    socket.close();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Error when closing socket", e);
+            }
+        }
+    }
+
+    /**
+     * Object representation of filters to be applied on message listing
+     *
+     * @see #getMessagesListing(String, int, MessagesFilter, int)
+     * @see #getMessagesListing(String, int, MessagesFilter, int, int, int)
+     */
+    public static final class MessagesFilter {
+
+        public final static byte MESSAGE_TYPE_ALL = 0x00;
+        public final static byte MESSAGE_TYPE_SMS_GSM = 0x01;
+        public final static byte MESSAGE_TYPE_SMS_CDMA = 0x02;
+        public final static byte MESSAGE_TYPE_EMAIL = 0x04;
+        public final static byte MESSAGE_TYPE_MMS = 0x08;
+
+        public final static byte READ_STATUS_ANY = 0x00;
+        public final static byte READ_STATUS_UNREAD = 0x01;
+        public final static byte READ_STATUS_READ = 0x02;
+
+        public final static byte PRIORITY_ANY = 0x00;
+        public final static byte PRIORITY_HIGH = 0x01;
+        public final static byte PRIORITY_NON_HIGH = 0x02;
+
+        byte messageType = MESSAGE_TYPE_ALL;
+
+        String periodBegin = null;
+
+        String periodEnd = null;
+
+        byte readStatus = READ_STATUS_ANY;
+
+        String recipient = null;
+
+        String originator = null;
+
+        byte priority = PRIORITY_ANY;
+
+        public MessagesFilter() {
+        }
+
+        public void setMessageType(byte filter) {
+            messageType = filter;
+        }
+
+        public void setPeriod(Date filterBegin, Date filterEnd) {
+            periodBegin = (new ObexTime(filterBegin)).toString();
+            periodEnd = (new ObexTime(filterEnd)).toString();
+        }
+
+        public void setReadStatus(byte readfilter) {
+            readStatus = readfilter;
+        }
+
+        public void setRecipient(String filter) {
+            if ("".equals(filter)) {
+                recipient = null;
+            } else {
+                recipient = filter;
+            }
+        }
+
+        public void setOriginator(String filter) {
+            if ("".equals(filter)) {
+                originator = null;
+            } else {
+                originator = filter;
+            }
+        }
+
+        public void setPriority(byte filter) {
+            priority = filter;
+        }
+    }
+
+    /**
+     * Constructs client object to communicate with single MAS instance on MSE
+     *
+     * @param device {@link BluetoothDevice} corresponding to remote device
+     *            acting as MSE
+     * @param mas {@link BluetoothMasInstance} object describing MAS instance on
+     *            remote device
+     * @param callback {@link Handler} object to which callback messages will be
+     *            sent Each message will have <code>arg1</code> set to either
+     *            {@link #STATUS_OK} or {@link #STATUS_FAILED} and
+     *            <code>arg2</code> to MAS instance ID. <code>obj</code> in
+     *            message is event specific.
+     */
+    public BluetoothMasClient(BluetoothDevice device, BluetoothMasInstance mas,
+            Handler callback) {
+        mDevice = device;
+        mMas = mas;
+        mCallback = callback;
+
+        mPath = new ArrayDeque<String>();
+    }
+
+    /**
+     * Retrieves MAS instance data associated with client
+     *
+     * @return instance data object
+     */
+    public BluetoothMasInstance getInstanceData() {
+        return mMas;
+    }
+
+    /**
+     * Connects to MAS instance
+     * <p>
+     * Upon completion callback handler will receive {@link #EVENT_CONNECT}
+     */
+    public void connect() {
+        if (mSessionHandler == null) {
+            mSessionHandler = new SessionHandler(this);
+        }
+
+        if (mConnectThread == null && mObexSession == null) {
+            mConnectionState = ConnectionState.CONNECTING;
+
+            mConnectThread = new SocketConnectThread();
+            mConnectThread.start();
+        }
+    }
+
+    /**
+     * Disconnects from MAS instance
+     * <p>
+     * Upon completion callback handler will receive {@link #EVENT_CONNECT}
+     */
+    public void disconnect() {
+        if (mConnectThread == null && mObexSession == null) {
+            return;
+        }
+
+        mConnectionState = ConnectionState.DISCONNECTING;
+
+        if (mConnectThread != null) {
+            mConnectThread.interrupt();
+        }
+
+        if (mObexSession != null) {
+            mObexSession.stop();
+        }
+    }
+
+    @Override
+    public void finalize() {
+        disconnect();
+    }
+
+    /**
+     * Gets current connection state
+     *
+     * @return current connection state
+     * @see ConnectionState
+     */
+    public ConnectionState getState() {
+        return mConnectionState;
+    }
+
+    private boolean enableNotifications() {
+        Log.v(TAG, "enableNotifications()");
+
+        if (mMnsService == null) {
+            mMnsService = new BluetoothMnsService();
+        }
+
+        mMnsService.registerCallback(mMas.getId(), mSessionHandler);
+
+        BluetoothMasRequest request = new BluetoothMasRequestSetNotificationRegistration(true);
+        return mObexSession.makeRequest(request);
+    }
+
+    private boolean disableNotifications() {
+        Log.v(TAG, "enableNotifications()");
+
+        if (mMnsService != null) {
+            mMnsService.unregisterCallback(mMas.getId());
+        }
+
+        mMnsService = null;
+
+        BluetoothMasRequest request = new BluetoothMasRequestSetNotificationRegistration(false);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Sets state of notifications for MAS instance
+     * <p>
+     * Once notifications are enabled, callback handler will receive
+     * {@link #EVENT_EVENT_REPORT} when new notification is received
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_SET_NOTIFICATION_REGISTRATION}
+     *
+     * @param status <code>true</code> if notifications shall be enabled,
+     *            <code>false</code> otherwise
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean setNotificationRegistration(boolean status) {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        if (status) {
+            return enableNotifications();
+        } else {
+            return disableNotifications();
+        }
+    }
+
+    /**
+     * Gets current state of notifications for MAS instance
+     *
+     * @return <code>true</code> if notifications are enabled,
+     *         <code>false</code> otherwise
+     */
+    public boolean getNotificationRegistration() {
+        return mNotificationEnabled;
+    }
+
+    /**
+     * Goes back to root of folder hierarchy
+     * <p>
+     * Upon completion callback handler will receive {@link #EVENT_SET_PATH}
+     *
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean setFolderRoot() {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestSetPath(true);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Goes back to parent folder in folder hierarchy
+     * <p>
+     * Upon completion callback handler will receive {@link #EVENT_SET_PATH}
+     *
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean setFolderUp() {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestSetPath(false);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Goes down to specified sub-folder in folder hierarchy
+     * <p>
+     * Upon completion callback handler will receive {@link #EVENT_SET_PATH}
+     *
+     * @param name name of sub-folder
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean setFolderDown(String name) {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        if (name == null || name.isEmpty() || name.contains("/")) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestSetPath(name);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Gets current path in folder hierarchy
+     *
+     * @return current path
+     */
+    public String getCurrentPath() {
+        if (mPath.size() == 0) {
+            return "";
+        }
+
+        Iterator<String> iter = mPath.iterator();
+
+        StringBuilder sb = new StringBuilder(iter.next());
+
+        while (iter.hasNext()) {
+            sb.append("/").append(iter.next());
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Gets list of sub-folders in current folder
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_GET_FOLDER_LISTING}
+     *
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean getFolderListing() {
+        return getFolderListing((short) 0, (short) 0);
+    }
+
+    /**
+     * Gets list of sub-folders in current folder
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_GET_FOLDER_LISTING}
+     *
+     * @param maxListCount maximum number of items returned or <code>0</code>
+     *            for default value
+     * @param listStartOffset index of first item returned or <code>0</code> for
+     *            default value
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     * @throws IllegalArgumentException if either maxListCount or
+     *             listStartOffset are outside allowed range [0..65535]
+     */
+    public boolean getFolderListing(int maxListCount, int listStartOffset) {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestGetFolderListing(maxListCount,
+                listStartOffset);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Gets number of sub-folders in current folder
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_GET_FOLDER_LISTING_SIZE}
+     *
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean getFolderListingSize() {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestGetFolderListingSize();
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Gets list of messages in specified sub-folder
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_GET_MESSAGES_LISTING}
+     *
+     * @param folder name of sub-folder or <code>null</code> for current folder
+     * @param parameters bit-mask specifying requested parameters in listing or
+     *            <code>0</code> for default value
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean getMessagesListing(String folder, int parameters) {
+        return getMessagesListing(folder, parameters, null, (byte) 0, 0, 0);
+    }
+
+    /**
+     * Gets list of messages in specified sub-folder
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_GET_MESSAGES_LISTING}
+     *
+     * @param folder name of sub-folder or <code>null</code> for current folder
+     * @param parameters corresponds to <code>ParameterMask</code> application
+     *            parameter in MAP specification
+     * @param filter {@link MessagesFilter} object describing filters to be
+     *            applied on listing by MSE
+     * @param subjectLength maximum length of message subject in returned
+     *            listing or <code>0</code> for default value
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     * @throws IllegalArgumentException if subjectLength is outside allowed
+     *             range [0..255]
+     */
+    public boolean getMessagesListing(String folder, int parameters, MessagesFilter filter,
+            int subjectLength) {
+
+        return getMessagesListing(folder, parameters, filter, subjectLength, 0, 0);
+    }
+
+    /**
+     * Gets list of messages in specified sub-folder
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_GET_MESSAGES_LISTING}
+     *
+     * @param folder name of sub-folder or <code>null</code> for current folder
+     * @param parameters corresponds to <code>ParameterMask</code> application
+     *            parameter in MAP specification
+     * @param filter {@link MessagesFilter} object describing filters to be
+     *            applied on listing by MSE
+     * @param subjectLength maximum length of message subject in returned
+     *            listing or <code>0</code> for default value
+     * @param maxListCount maximum number of items returned or <code>0</code>
+     *            for default value
+     * @param listStartOffset index of first item returned or <code>0</code> for
+     *            default value
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     * @throws IllegalArgumentException if subjectLength is outside allowed
+     *             range [0..255] or either maxListCount or listStartOffset are
+     *             outside allowed range [0..65535]
+     */
+    public boolean getMessagesListing(String folder, int parameters, MessagesFilter filter,
+            int subjectLength, int maxListCount, int listStartOffset) {
+
+        if (mObexSession == null) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestGetMessagesListing(folder,
+                parameters, filter, subjectLength, maxListCount, listStartOffset);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Gets number of messages in current folder
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_GET_MESSAGES_LISTING_SIZE}
+     *
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean getMessagesListingSize() {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestGetMessagesListingSize();
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Retrieves message from MSE
+     * <p>
+     * Upon completion callback handler will receive {@link #EVENT_GET_MESSAGE}
+     *
+     * @param handle handle of message to retrieve
+     * @param charset {@link CharsetType} object corresponding to
+     *            <code>Charset</code> application parameter in MAP
+     *            specification
+     * @param attachment corresponds to <code>Attachment</code> application
+     *            parameter in MAP specification
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean getMessage(String handle, CharsetType charset, boolean attachment) {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        try {
+            /* just to validate */
+            new BigInteger(handle, 16);
+        } catch (NumberFormatException e) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestGetMessage(handle, charset,
+                attachment);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Sets read status of message on MSE
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_SET_MESSAGE_STATUS}
+     *
+     * @param handle handle of message
+     * @param read <code>true</code> for "read", <code>false</code> for "unread"
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean setMessageReadStatus(String handle, boolean read) {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        try {
+            /* just to validate */
+            new BigInteger(handle, 16);
+        } catch (NumberFormatException e) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestSetMessageStatus(handle,
+                StatusIndicator.READ, read);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Sets deleted status of message on MSE
+     * <p>
+     * Upon completion callback handler will receive
+     * {@link #EVENT_SET_MESSAGE_STATUS}
+     *
+     * @param handle handle of message
+     * @param deleted <code>true</code> for "deleted", <code>false</code> for
+     *            "undeleted"
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean setMessageDeletedStatus(String handle, boolean deleted) {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        try {
+            /* just to validate */
+            new BigInteger(handle, 16);
+        } catch (NumberFormatException e) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestSetMessageStatus(handle,
+                StatusIndicator.DELETED, deleted);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Pushes new message to MSE
+     * <p>
+     * Upon completion callback handler will receive {@link #EVENT_PUSH_MESSAGE}
+     *
+     * @param folder name of sub-folder to push to or <code>null</code> for
+     *            current folder
+     * @param charset {@link CharsetType} object corresponding to
+     *            <code>Charset</code> application parameter in MAP
+     *            specification
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset) {
+        return pushMessage(folder, bmsg, charset, false, false);
+    }
+
+    /**
+     * Pushes new message to MSE
+     * <p>
+     * Upon completion callback handler will receive {@link #EVENT_PUSH_MESSAGE}
+     *
+     * @param folder name of sub-folder to push to or <code>null</code> for
+     *            current folder
+     * @param bmsg {@link BluetoothMapBmessage} object representing message to
+     *            be pushed
+     * @param charset {@link CharsetType} object corresponding to
+     *            <code>Charset</code> application parameter in MAP
+     *            specification
+     * @param transparent corresponds to <code>Transparent</code> application
+     *            parameter in MAP specification
+     * @param retry corresponds to <code>Transparent</code> application
+     *            parameter in MAP specification
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset,
+            boolean transparent, boolean retry) {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        String bmsgString = BluetoothMapBmessageBuilder.createBmessage(bmsg);
+
+        BluetoothMasRequest request =
+                new BluetoothMasRequestPushMessage(folder, bmsgString, charset, transparent, retry);
+        return mObexSession.makeRequest(request);
+    }
+
+    /**
+     * Requests MSE to initiate ubdate of inbox
+     * <p>
+     * Upon completion callback handler will receive {@link #EVENT_UPDATE_INBOX}
+     *
+     * @return <code>true</code> if request has been sent, <code>false</code>
+     *         otherwise
+     */
+    public boolean updateInbox() {
+        if (mObexSession == null) {
+            return false;
+        }
+
+        BluetoothMasRequest request = new BluetoothMasRequestUpdateInbox();
+        return mObexSession.makeRequest(request);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasObexClientSession.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasObexClientSession.java
new file mode 100644
index 0000000..da95276
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasObexClientSession.java
@@ -0,0 +1,205 @@
+/*
+ * 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.mapclient;
+
+import android.os.Handler;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ObexTransport;
+import javax.obex.ResponseCodes;
+
+class BluetoothMasObexClientSession {
+    private static final String TAG = "BluetoothMasObexClientSession";
+
+    private static final byte[] MAS_TARGET = new byte[] {
+            (byte) 0xbb, 0x58, 0x2b, 0x40, 0x42, 0x0c, 0x11, (byte) 0xdb, (byte) 0xb0, (byte) 0xde,
+            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
+    };
+
+    static final int MSG_OBEX_CONNECTED = 100;
+    static final int MSG_OBEX_DISCONNECTED = 101;
+    static final int MSG_REQUEST_COMPLETED = 102;
+
+    private final ObexTransport mTransport;
+
+    private final Handler mSessionHandler;
+
+    private ClientThread mClientThread;
+
+    private volatile boolean mInterrupted;
+
+    private class ClientThread extends Thread {
+        private final ObexTransport mTransport;
+
+        private ClientSession mSession;
+
+        private BluetoothMasRequest mRequest;
+
+        private boolean mConnected;
+
+        public ClientThread(ObexTransport transport) {
+            super("MAS ClientThread");
+
+            mTransport = transport;
+            mConnected = false;
+        }
+
+        @Override
+        public void run() {
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+            connect();
+
+            if (mConnected) {
+                mSessionHandler.obtainMessage(MSG_OBEX_CONNECTED).sendToTarget();
+            } else {
+                mSessionHandler.obtainMessage(MSG_OBEX_DISCONNECTED).sendToTarget();
+                return;
+            }
+
+            while (!mInterrupted) {
+                synchronized (this) {
+                    if (mRequest == null) {
+                        try {
+                            this.wait();
+                        } catch (InterruptedException e) {
+                            mInterrupted = true;
+                        }
+                    }
+                }
+
+                if (!mInterrupted && mRequest != null) {
+                    try {
+                        mRequest.execute(mSession);
+                    } catch (IOException e) {
+                        // this will "disconnect" to cleanup
+                        mInterrupted = true;
+                    }
+
+                    BluetoothMasRequest oldReq = mRequest;
+                    mRequest = null;
+
+                    mSessionHandler.obtainMessage(MSG_REQUEST_COMPLETED, oldReq).sendToTarget();
+                }
+            }
+
+            disconnect();
+
+            mSessionHandler.obtainMessage(MSG_OBEX_DISCONNECTED).sendToTarget();
+        }
+
+        private void connect() {
+            try {
+                mSession = new ClientSession(mTransport);
+
+                HeaderSet headerset = new HeaderSet();
+                headerset.setHeader(HeaderSet.TARGET, MAS_TARGET);
+
+                headerset = mSession.connect(headerset);
+
+                if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) {
+                    mConnected = true;
+                } else {
+                    disconnect();
+                }
+            } catch (IOException e) {
+            }
+        }
+
+        private void disconnect() {
+            try {
+                mSession.disconnect(null);
+            } catch (IOException e) {
+            }
+
+            try {
+                mSession.close();
+            } catch (IOException e) {
+            }
+
+            mConnected = false;
+        }
+
+        public synchronized boolean schedule(BluetoothMasRequest request) {
+            if (mRequest != null) {
+                return false;
+            }
+
+            mRequest = request;
+            notify();
+
+            return true;
+        }
+    }
+
+    public BluetoothMasObexClientSession(ObexTransport transport, Handler handler) {
+        mTransport = transport;
+        mSessionHandler = handler;
+    }
+
+    public void start() {
+        if (mClientThread == null) {
+            mClientThread = new ClientThread(mTransport);
+            mClientThread.start();
+        }
+
+    }
+
+    public void stop() {
+        if (mClientThread != null) {
+            mClientThread.interrupt();
+
+            (new Thread() {
+                @Override
+                public void run() {
+                    try {
+                        mClientThread.join();
+                        mClientThread = null;
+                    } catch (InterruptedException e) {
+                        Log.w(TAG, "Interrupted while waiting for thread to join");
+                    }
+                }
+            }).run();
+        }
+    }
+
+    public boolean makeRequest(BluetoothMasRequest request) {
+        if (mClientThread == null) {
+            return false;
+        }
+
+        return mClientThread.schedule(request);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequest.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequest.java
new file mode 100644
index 0000000..ed4e579
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.mapclient;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.obex.ClientOperation;
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+
+abstract class BluetoothMasRequest {
+
+    protected static final byte OAP_TAGID_MAX_LIST_COUNT = 0x01;
+    protected static final byte OAP_TAGID_START_OFFSET = 0x02;
+    protected static final byte OAP_TAGID_FILTER_MESSAGE_TYPE = 0x03;
+    protected static final byte OAP_TAGID_FILTER_PERIOD_BEGIN = 0x04;
+    protected static final byte OAP_TAGID_FILTER_PERIOD_END = 0x05;
+    protected static final byte OAP_TAGID_FILTER_READ_STATUS = 0x06;
+    protected static final byte OAP_TAGID_FILTER_RECIPIENT = 0x07;
+    protected static final byte OAP_TAGID_FILTER_ORIGINATOR = 0x08;
+    protected static final byte OAP_TAGID_FILTER_PRIORITY = 0x09;
+    protected static final byte OAP_TAGID_ATTACHMENT = 0x0a;
+    protected static final byte OAP_TAGID_TRANSPARENT = 0xb;
+    protected static final byte OAP_TAGID_RETRY = 0xc;
+    protected static final byte OAP_TAGID_NEW_MESSAGE = 0x0d;
+    protected static final byte OAP_TAGID_NOTIFICATION_STATUS = 0x0e;
+    protected static final byte OAP_TAGID_MAS_INSTANCE_ID = 0x0f;
+    protected static final byte OAP_TAGID_FOLDER_LISTING_SIZE = 0x11;
+    protected static final byte OAP_TAGID_MESSAGES_LISTING_SIZE = 0x12;
+    protected static final byte OAP_TAGID_SUBJECT_LENGTH = 0x13;
+    protected static final byte OAP_TAGID_CHARSET = 0x14;
+    protected static final byte OAP_TAGID_STATUS_INDICATOR = 0x17;
+    protected static final byte OAP_TAGID_STATUS_VALUE = 0x18;
+    protected static final byte OAP_TAGID_MSE_TIME = 0x19;
+
+    protected static byte NOTIFICATION_ON = 0x01;
+    protected static byte NOTIFICATION_OFF = 0x00;
+
+    protected static byte ATTACHMENT_ON = 0x01;
+    protected static byte ATTACHMENT_OFF = 0x00;
+
+    protected static byte CHARSET_NATIVE = 0x00;
+    protected static byte CHARSET_UTF8 = 0x01;
+
+    protected static byte STATUS_INDICATOR_READ = 0x00;
+    protected static byte STATUS_INDICATOR_DELETED = 0x01;
+
+    protected static byte STATUS_NO = 0x00;
+    protected static byte STATUS_YES = 0x01;
+
+    protected static byte TRANSPARENT_OFF = 0x00;
+    protected static byte TRANSPARENT_ON = 0x01;
+
+    protected static byte RETRY_OFF = 0x00;
+    protected static byte RETRY_ON = 0x01;
+
+    /* used for PUT requests which require filler byte */
+    protected static final byte[] FILLER_BYTE = {
+        0x30
+    };
+
+    protected HeaderSet mHeaderSet;
+
+    protected int mResponseCode;
+
+    public BluetoothMasRequest() {
+        mHeaderSet = new HeaderSet();
+    }
+
+    abstract public void execute(ClientSession session) throws IOException;
+
+    protected void executeGet(ClientSession session) throws IOException {
+        ClientOperation op = null;
+
+        try {
+            op = (ClientOperation) session.get(mHeaderSet);
+
+            /*
+             * MAP spec does not explicitly require that GET request should be
+             * sent in single packet but for some reason PTS complains when
+             * final GET packet with no headers follows non-final GET with all
+             * headers. So this is workaround, at least temporary. TODO: check
+             * with PTS
+             */
+            op.setGetFinalFlag(true);
+
+            /*
+             * this will trigger ClientOperation to use non-buffered stream so
+             * we can abort operation
+             */
+            op.continueOperation(true, false);
+
+            readResponseHeaders(op.getReceivedHeader());
+
+            InputStream is = op.openInputStream();
+            readResponse(is);
+            is.close();
+
+            op.close();
+
+            mResponseCode = op.getResponseCode();
+        } catch (IOException e) {
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+
+            throw e;
+        }
+    }
+
+    protected void executePut(ClientSession session, byte[] body) throws IOException {
+        Operation op = null;
+
+        mHeaderSet.setHeader(HeaderSet.LENGTH, Long.valueOf(body.length));
+
+        try {
+            op = session.put(mHeaderSet);
+
+            DataOutputStream out = op.openDataOutputStream();
+            out.write(body);
+            out.close();
+
+            readResponseHeaders(op.getReceivedHeader());
+
+            op.close();
+            mResponseCode = op.getResponseCode();
+        } catch (IOException e) {
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+
+            throw e;
+        }
+    }
+
+    final public boolean isSuccess() {
+        return (mResponseCode == ResponseCodes.OBEX_HTTP_OK);
+    }
+
+    protected void readResponse(InputStream stream) throws IOException {
+        /* nothing here by default */
+    }
+
+    protected void readResponseHeaders(HeaderSet headerset) {
+        /* nothing here by default */
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetFolderListing.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetFolderListing.java
new file mode 100644
index 0000000..225d26d
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetFolderListing.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.mapclient;
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+
+final class BluetoothMasRequestGetFolderListing extends BluetoothMasRequest {
+
+    private static final String TYPE = "x-obex/folder-listing";
+
+    private BluetoothMapFolderListing mResponse = null;
+
+    public BluetoothMasRequestGetFolderListing(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.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        if (maxListCount > 0) {
+            oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) maxListCount);
+        }
+
+        if (listStartOffset > 0) {
+            oap.add(OAP_TAGID_START_OFFSET, (short) listStartOffset);
+        }
+
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponse(InputStream stream) {
+        mResponse = new BluetoothMapFolderListing(stream);
+    }
+
+    public ArrayList<String> getList() {
+        if (mResponse == null) {
+            return null;
+        }
+
+        return mResponse.getList();
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executeGet(session);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetFolderListingSize.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetFolderListingSize.java
new file mode 100644
index 0000000..8915e48
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetFolderListingSize.java
@@ -0,0 +1,69 @@
+/*
+ * 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.mapclient;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+
+final class BluetoothMasRequestGetFolderListingSize extends BluetoothMasRequest {
+
+    private static final String TYPE = "x-obex/folder-listing";
+
+    private int mSize;
+
+    public BluetoothMasRequestGetFolderListingSize() {
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+        oap.add(OAP_TAGID_MAX_LIST_COUNT, 0);
+
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        mSize = oap.getShort(OAP_TAGID_FOLDER_LISTING_SIZE);
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executeGet(session);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetMessage.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetMessage.java
new file mode 100644
index 0000000..21bc040
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetMessage.java
@@ -0,0 +1,101 @@
+/*
+ * 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.mapclient;
+
+import android.util.Log;
+
+
+import org.codeaurora.bluetooth.mapclient.BluetoothMasClient.CharsetType;
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+final class BluetoothMasRequestGetMessage extends BluetoothMasRequest {
+
+    private static final String TAG = "BluetoothMasRequestGetMessage";
+
+    private static final String TYPE = "x-bt/message";
+
+    private BluetoothMapBmessage mBmessage;
+
+    public BluetoothMasRequestGetMessage(String handle, CharsetType charset, boolean attachment) {
+
+        mHeaderSet.setHeader(HeaderSet.NAME, handle);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        oap.add(OAP_TAGID_CHARSET, CharsetType.UTF_8.equals(charset) ? CHARSET_UTF8
+                : CHARSET_NATIVE);
+
+        oap.add(OAP_TAGID_ATTACHMENT, attachment ? ATTACHMENT_ON : ATTACHMENT_OFF);
+
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponse(InputStream stream) {
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buf = new byte[1024];
+
+        try {
+            int len;
+            while ((len = stream.read(buf)) != -1) {
+                baos.write(buf, 0, len);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "I/O exception while reading response", e);
+        }
+
+        String bmsg = baos.toString();
+
+        mBmessage = BluetoothMapBmessageParser.createBmessage(bmsg);
+
+        if (mBmessage == null) {
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        }
+    }
+
+    public BluetoothMapBmessage getMessage() {
+        return mBmessage;
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executeGet(session);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetMessagesListing.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetMessagesListing.java
new file mode 100644
index 0000000..c44810a
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetMessagesListing.java
@@ -0,0 +1,163 @@
+/*
+ * 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.mapclient;
+
+import org.codeaurora.bluetooth.mapclient.BluetoothMasClient.MessagesFilter;
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+import org.codeaurora.bluetooth.utils.ObexTime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Date;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+
+final class BluetoothMasRequestGetMessagesListing extends BluetoothMasRequest {
+
+    private static final String TYPE = "x-bt/MAP-msg-listing";
+
+    private BluetoothMapMessagesListing mResponse = null;
+
+    private boolean mNewMessage = false;
+
+    private Date mServerTime = null;
+
+    public BluetoothMasRequestGetMessagesListing(String folderName, int parameters,
+            BluetoothMasClient.MessagesFilter filter, int subjectLength, int maxListCount,
+            int listStartOffset) {
+
+        if (subjectLength < 0 || subjectLength > 255) {
+            throw new IllegalArgumentException("subjectLength should be [0..255]");
+        }
+
+        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.TYPE, TYPE);
+
+        if (folderName == null) {
+            mHeaderSet.setHeader(HeaderSet.NAME, "");
+        } else {
+            mHeaderSet.setHeader(HeaderSet.NAME, folderName);
+        }
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        if (filter != null) {
+            if (filter.messageType != MessagesFilter.MESSAGE_TYPE_ALL) {
+                oap.add(OAP_TAGID_FILTER_MESSAGE_TYPE, filter.messageType);
+            }
+
+            if (filter.periodBegin != null) {
+                oap.add(OAP_TAGID_FILTER_PERIOD_BEGIN, filter.periodBegin);
+            }
+
+            if (filter.periodEnd != null) {
+                oap.add(OAP_TAGID_FILTER_PERIOD_END, filter.periodEnd);
+            }
+
+            if (filter.readStatus != MessagesFilter.READ_STATUS_ANY) {
+                oap.add(OAP_TAGID_FILTER_READ_STATUS, filter.readStatus);
+            }
+
+            if (filter.recipient != null) {
+                oap.add(OAP_TAGID_FILTER_RECIPIENT, filter.recipient);
+            }
+
+            if (filter.originator != null) {
+                oap.add(OAP_TAGID_FILTER_ORIGINATOR, filter.originator);
+            }
+
+            if (filter.priority != MessagesFilter.PRIORITY_ANY) {
+                oap.add(OAP_TAGID_FILTER_PRIORITY, filter.priority);
+            }
+        }
+
+        if (subjectLength != 0) {
+            oap.add(OAP_TAGID_SUBJECT_LENGTH, (byte) subjectLength);
+        }
+
+        if (maxListCount != 0) {
+            oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) maxListCount);
+        }
+
+        if (listStartOffset != 0) {
+            oap.add(OAP_TAGID_START_OFFSET, (short) listStartOffset);
+        }
+
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponse(InputStream stream) {
+        mResponse = new BluetoothMapMessagesListing(stream);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        mNewMessage = ((oap.getByte(OAP_TAGID_NEW_MESSAGE) & 0x01) == 1);
+
+        if (oap.exists(OAP_TAGID_MSE_TIME)) {
+            String mseTime = oap.getString(OAP_TAGID_MSE_TIME);
+
+            mServerTime = (new ObexTime(mseTime)).getTime();
+        }
+    }
+
+    public ArrayList<BluetoothMapMessage> getList() {
+        if (mResponse == null) {
+            return null;
+        }
+
+        return mResponse.getList();
+    }
+
+    public boolean getNewMessageStatus() {
+        return mNewMessage;
+    }
+
+    public Date getMseTime() {
+        return mServerTime;
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executeGet(session);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetMessagesListingSize.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetMessagesListingSize.java
new file mode 100644
index 0000000..464a093
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestGetMessagesListingSize.java
@@ -0,0 +1,70 @@
+/*
+ * 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.mapclient;
+
+import  org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+
+final class BluetoothMasRequestGetMessagesListingSize extends BluetoothMasRequest {
+
+    private static final String TYPE = "x-bt/MAP-msg-listing";
+
+    private int mSize;
+
+    public BluetoothMasRequestGetMessagesListingSize() {
+        mHeaderSet.setHeader(HeaderSet.NAME, "");
+        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) {
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        mSize = oap.getShort(OAP_TAGID_MESSAGES_LISTING_SIZE);
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executeGet(session);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestPushMessage.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestPushMessage.java
new file mode 100644
index 0000000..4eda451
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestPushMessage.java
@@ -0,0 +1,92 @@
+/*
+ * 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.mapclient;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+import org.codeaurora.bluetooth.mapclient.BluetoothMasClient.CharsetType;
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+final class BluetoothMasRequestPushMessage extends BluetoothMasRequest {
+
+    private static final String TYPE = "x-bt/message";
+    private String mMsg;
+    private String mMsgHandle;
+
+    private BluetoothMasRequestPushMessage(String folder) {
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+        if (folder == null) {
+            folder = "";
+        }
+        mHeaderSet.setHeader(HeaderSet.NAME, folder);
+    }
+
+    public BluetoothMasRequestPushMessage(String folder, String msg, CharsetType charset,
+            boolean transparent, boolean retry) {
+        this(folder);
+        mMsg = msg;
+        ObexAppParameters oap = new ObexAppParameters();
+        oap.add(OAP_TAGID_TRANSPARENT, transparent ? TRANSPARENT_ON : TRANSPARENT_OFF);
+        oap.add(OAP_TAGID_RETRY, retry ? RETRY_ON : RETRY_OFF);
+        oap.add(OAP_TAGID_CHARSET, charset == CharsetType.NATIVE ? CHARSET_NATIVE : CHARSET_UTF8);
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        try {
+            String handle = (String) headerset.getHeader(HeaderSet.NAME);
+            if (handle != null) {
+                /* just to validate */
+                new BigInteger(handle, 16);
+
+                mMsgHandle = handle;
+            }
+        } catch (NumberFormatException e) {
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        } catch (IOException e) {
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        }
+    }
+
+    public String getMsgHandle() {
+        return mMsgHandle;
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executePut(session, mMsg.getBytes());
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestSetMessageStatus.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestSetMessageStatus.java
new file mode 100644
index 0000000..367f88e
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestSetMessageStatus.java
@@ -0,0 +1,64 @@
+/*
+ * 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.mapclient;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+
+final class BluetoothMasRequestSetMessageStatus extends BluetoothMasRequest {
+
+    public enum StatusIndicator {
+        READ, DELETED;
+    }
+
+    private static final String TYPE = "x-bt/messageStatus";
+
+    public BluetoothMasRequestSetMessageStatus(String handle, StatusIndicator statusInd,
+            boolean statusValue) {
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+        mHeaderSet.setHeader(HeaderSet.NAME, handle);
+
+        ObexAppParameters oap = new ObexAppParameters();
+        oap.add(OAP_TAGID_STATUS_INDICATOR,
+                statusInd == StatusIndicator.READ ? STATUS_INDICATOR_READ
+                        : STATUS_INDICATOR_DELETED);
+        oap.add(OAP_TAGID_STATUS_VALUE, statusValue ? STATUS_YES : STATUS_NO);
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executePut(session, FILLER_BYTE);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestSetNotificationRegistration.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestSetNotificationRegistration.java
new file mode 100644
index 0000000..b62d7af
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestSetNotificationRegistration.java
@@ -0,0 +1,65 @@
+/*
+ * 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.mapclient;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+
+final class BluetoothMasRequestSetNotificationRegistration extends BluetoothMasRequest {
+
+    private static final String TYPE = "x-bt/MAP-NotificationRegistration";
+
+    private final boolean mStatus;
+
+    public BluetoothMasRequestSetNotificationRegistration(boolean status) {
+        mStatus = status;
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        oap.add(OAP_TAGID_NOTIFICATION_STATUS, status ? NOTIFICATION_ON : NOTIFICATION_OFF);
+
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executePut(session, FILLER_BYTE);
+    }
+
+    public boolean getStatus() {
+        return mStatus;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestSetPath.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestSetPath.java
new file mode 100644
index 0000000..507224f
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestSetPath.java
@@ -0,0 +1,84 @@
+/*
+ * 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.mapclient;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+class BluetoothMasRequestSetPath extends BluetoothMasRequest {
+
+    enum SetPathDir {
+        ROOT, UP, DOWN
+    };
+
+    SetPathDir mDir;
+
+    String mName;
+
+    public BluetoothMasRequestSetPath(String name) {
+        mDir = SetPathDir.DOWN;
+        mName = name;
+
+        mHeaderSet.setHeader(HeaderSet.NAME, name);
+    }
+
+    public BluetoothMasRequestSetPath(boolean goRoot) {
+        mHeaderSet.setEmptyNameHeader();
+        if (goRoot) {
+            mDir = SetPathDir.ROOT;
+        } else {
+            mDir = SetPathDir.UP;
+        }
+    }
+
+    @Override
+    public void execute(ClientSession session) {
+        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/mapclient/BluetoothMasRequestUpdateInbox.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestUpdateInbox.java
new file mode 100644
index 0000000..2b05dc0
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMasRequestUpdateInbox.java
@@ -0,0 +1,49 @@
+/*
+ * 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.mapclient;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+
+final class BluetoothMasRequestUpdateInbox extends BluetoothMasRequest {
+
+    private static final String TYPE = "x-bt/MAP-messageUpdate";
+
+    public BluetoothMasRequestUpdateInbox() {
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executePut(session, FILLER_BYTE);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMnsObexServer.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMnsObexServer.java
new file mode 100644
index 0000000..9bf8fb5
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMnsObexServer.java
@@ -0,0 +1,148 @@
+/*
+ * 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.mapclient;
+
+import android.os.Handler;
+import android.util.Log;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+import javax.obex.ServerRequestHandler;
+
+class BluetoothMnsObexServer extends ServerRequestHandler {
+
+    private final static String TAG = "BluetoothMnsObexServer";
+
+    private static final byte[] MNS_TARGET = new byte[] {
+            (byte) 0xbb, 0x58, 0x2b, 0x41, 0x42, 0x0c, 0x11, (byte) 0xdb, (byte) 0xb0, (byte) 0xde,
+            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
+    };
+
+    private final static String TYPE = "x-bt/MAP-event-report";
+
+    private final Handler mCallback;
+
+    public BluetoothMnsObexServer(Handler callback) {
+        super();
+
+        mCallback = callback;
+    }
+
+    @Override
+    public int onConnect(final HeaderSet request, HeaderSet reply) {
+        Log.v(TAG, "onConnect");
+
+        try {
+            byte[] uuid = (byte[]) request.getHeader(HeaderSet.TARGET);
+
+            if (!Arrays.equals(uuid, MNS_TARGET)) {
+                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+            }
+
+        } catch (IOException e) {
+            // this should never happen since getHeader won't throw exception it
+            // declares to throw
+            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        }
+
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    @Override
+    public void onDisconnect(final HeaderSet request, HeaderSet reply) {
+        Log.v(TAG, "onDisconnect");
+    }
+
+    @Override
+    public int onGet(final Operation op) {
+        Log.v(TAG, "onGet");
+
+        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+    }
+
+    @Override
+    public int onPut(final Operation op) {
+        Log.v(TAG, "onPut");
+
+        try {
+            HeaderSet headerset;
+            headerset = op.getReceivedHeader();
+
+            String type = (String) headerset.getHeader(HeaderSet.TYPE);
+            ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+            if (!TYPE.equals(type) || !oap.exists(BluetoothMasRequest.OAP_TAGID_MAS_INSTANCE_ID)) {
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+
+            Byte inst = oap.getByte(BluetoothMasRequest.OAP_TAGID_MAS_INSTANCE_ID);
+
+            BluetoothMapEventReport ev = BluetoothMapEventReport.fromStream(op
+                    .openDataInputStream());
+
+            op.close();
+
+            mCallback.obtainMessage(BluetoothMnsService.MSG_EVENT, inst, 0, ev).sendToTarget();
+        } catch (IOException e) {
+            Log.e(TAG, "I/O exception when handling PUT request", e);
+            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        }
+
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    @Override
+    public int onAbort(final HeaderSet request, HeaderSet reply) {
+        Log.v(TAG, "onAbort");
+
+        return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+    }
+
+    @Override
+    public int onSetPath(final HeaderSet request, HeaderSet reply,
+            final boolean backup, final boolean create) {
+        Log.v(TAG, "onSetPath");
+
+        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+    }
+
+    @Override
+    public void onClose() {
+        Log.v(TAG, "onClose");
+
+        // TODO: call session handler so it can disconnect
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/BluetoothMnsService.java b/src/org/codeaurora/bluetooth/mapclient/BluetoothMnsService.java
new file mode 100644
index 0000000..125d37a
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/BluetoothMnsService.java
@@ -0,0 +1,209 @@
+/*
+ * 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.mapclient;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.lang.ref.WeakReference;
+
+import javax.obex.ServerSession;
+
+class BluetoothMnsService {
+
+    private static final String TAG = "BluetoothMnsService";
+
+    private static final ParcelUuid MAP_MNS =
+            ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
+
+    static final int MSG_EVENT = 1;
+
+    /* for BluetoothMasClient */
+    static final int EVENT_REPORT = 1001;
+
+    /* these are shared across instances */
+    static private SparseArray<Handler> mCallbacks = null;
+    static private SocketAcceptThread mAcceptThread = null;
+    static private Handler mSessionHandler = null;
+    static private BluetoothServerSocket mServerSocket = null;
+
+    private static class SessionHandler extends Handler {
+
+        private final WeakReference<BluetoothMnsService> mService;
+
+        SessionHandler(BluetoothMnsService service) {
+            mService = new WeakReference<BluetoothMnsService>(service);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Log.d(TAG, "Handler: msg: " + msg.what);
+
+            switch (msg.what) {
+                case MSG_EVENT:
+                    int instanceId = msg.arg1;
+
+                    synchronized (mCallbacks) {
+                        Handler cb = mCallbacks.get(instanceId);
+
+                        if (cb != null) {
+                            BluetoothMapEventReport ev = (BluetoothMapEventReport) msg.obj;
+                            cb.obtainMessage(EVENT_REPORT, ev).sendToTarget();
+                        } else {
+                            Log.w(TAG, "Got event for instance which is not registered: "
+                                    + instanceId);
+                        }
+                    }
+                    break;
+            }
+        }
+    }
+
+    private static class SocketAcceptThread extends Thread {
+
+        private boolean mInterrupted = false;
+
+        @Override
+        public void run() {
+
+            if (mServerSocket != null) {
+                Log.w(TAG, "Socket already created, exiting");
+                return;
+            }
+
+            try {
+                BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+                mServerSocket = adapter.listenUsingEncryptedRfcommWithServiceRecord(
+                        "MAP Message Notification Service", MAP_MNS.getUuid());
+            } catch (IOException e) {
+                mInterrupted = true;
+                Log.e(TAG, "I/O exception when trying to create server socket", e);
+            }
+
+            while (!mInterrupted) {
+                try {
+                    Log.v(TAG, "waiting to accept connection...");
+
+                    BluetoothSocket sock = mServerSocket.accept();
+
+                    Log.v(TAG, "new incoming connection from "
+                            + sock.getRemoteDevice().getName());
+
+                    // session will live until closed by remote
+                    BluetoothMnsObexServer srv = new BluetoothMnsObexServer(mSessionHandler);
+                    BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(
+                            sock);
+                    new ServerSession(transport, srv, null);
+                } catch (IOException ex) {
+                    Log.v(TAG, "I/O exception when waiting to accept (aborted?)");
+                    mInterrupted = true;
+                }
+            }
+
+            if (mServerSocket != null) {
+                try {
+                    mServerSocket.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+
+                mServerSocket = null;
+            }
+        }
+    }
+
+    BluetoothMnsService() {
+        Log.v(TAG, "BluetoothMnsService()");
+
+        if (mCallbacks == null) {
+            Log.v(TAG, "BluetoothMnsService(): allocating callbacks");
+            mCallbacks = new SparseArray<Handler>();
+        }
+
+        if (mSessionHandler == null) {
+            Log.v(TAG, "BluetoothMnsService(): allocating session handler");
+            mSessionHandler = new SessionHandler(this);
+        }
+    }
+
+    public void registerCallback(int instanceId, Handler callback) {
+        Log.v(TAG, "registerCallback()");
+
+        synchronized (mCallbacks) {
+            mCallbacks.put(instanceId, callback);
+
+            if (mAcceptThread == null) {
+                Log.v(TAG, "registerCallback(): starting MNS server");
+                mAcceptThread = new SocketAcceptThread();
+                mAcceptThread.setName("BluetoothMnsAcceptThread");
+                mAcceptThread.start();
+            }
+        }
+    }
+
+    public void unregisterCallback(int instanceId) {
+        Log.v(TAG, "unregisterCallback()");
+
+        synchronized (mCallbacks) {
+            mCallbacks.remove(instanceId);
+
+            if (mCallbacks.size() == 0) {
+                Log.v(TAG, "unregisterCallback(): shutting down MNS server");
+
+                if (mServerSocket != null) {
+                    try {
+                        mServerSocket.close();
+                    } catch (IOException e) {
+                    }
+
+                    mServerSocket = null;
+                }
+
+                mAcceptThread.interrupt();
+
+                try {
+                    mAcceptThread.join(5000);
+                } catch (InterruptedException e) {
+                }
+
+                mAcceptThread = null;
+            }
+        }
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/mapclient/utils/BmsgTokenizer.java b/src/org/codeaurora/bluetooth/mapclient/utils/BmsgTokenizer.java
new file mode 100644
index 0000000..6a6558c
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/utils/BmsgTokenizer.java
@@ -0,0 +1,120 @@
+/*
+ * 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/mapclient/utils/ObexAppParameters.java b/src/org/codeaurora/bluetooth/mapclient/utils/ObexAppParameters.java
new file mode 100644
index 0000000..4db0398
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/utils/ObexAppParameters.java
@@ -0,0 +1,194 @@
+/*
+ * 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/mapclient/utils/ObexTime.java b/src/org/codeaurora/bluetooth/mapclient/utils/ObexTime.java
new file mode 100644
index 0000000..18c0c8e
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/mapclient/utils/ObexTime.java
@@ -0,0 +1,114 @@
+/*
+ * 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));
+    }
+}