Merge "MAP: Get first recipient name of multiple recipients for SMS Drafts."
diff --git a/Android.mk b/Android.mk
index 7ae59ea..bbd17ec 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,5 @@
 
 include $(BUILD_PACKAGE)
 
+
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/bttestapp/Android.mk b/bttestapp/Android.mk
new file mode 100644
index 0000000..f560163
--- /dev/null
+++ b/bttestapp/Android.mk
@@ -0,0 +1,50 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+        $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := BTTestApp
+LOCAL_CERTIFICATE := platform
+
+LOCAL_MODULE_OWNER := qcom
+
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard org.codeaurora.bluetooth.mapclient org.codeaurora.bluetooth.pbapclient
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+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 $(CLEAR_VARS)
+
+src_dirs:=  ../src/org/codeaurora/bluetooth/pbapclient
+
+LOCAL_SRC_FILES := \
+        $(call all-java-files-under, $(src_dirs))
+
+LOCAL_MODULE:= org.codeaurora.bluetooth.pbapclient
+LOCAL_JAVA_LIBRARIES := javax.obex
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/bttestapp/AndroidManifest.xml b/bttestapp/AndroidManifest.xml
new file mode 100644
index 0000000..f96e4a5
--- /dev/null
+++ b/bttestapp/AndroidManifest.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.codeaurora.bluetooth.bttestapp"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.BLUETOOTH_STACK" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <uses-sdk
+        android:minSdkVersion="17"
+        android:targetSdkVersion="17" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+
+        <uses-library android:name="javax.obex" />
+
+        <activity android:name=".MainActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".VcardFilterActivity" />
+        <!--activity
+            android:name=".HfpTestActivity"
+            android:launchMode="singleTop" /-->
+        <activity
+            android:name=".PbapTestActivity"
+            android:launchMode="singleTop" />
+        <activity
+            android:name=".MapTestActivity"
+            android:launchMode="singleTop" />
+        <activity android:name=".MessageFilterActivity" />
+
+        <service android:name=".ProfileService" />
+<!--
+        <service android:name=".mapclient.BluetoothMnsService">
+            <intent-filter>
+                <action android:name="org.codeaurora.bluetooth.mapclient.BluetoothMnsService" />
+            </intent-filter>
+        </service>
+-->
+        <activity android:name=".services.PbapAuthActivity"
+            android:excludeFromRecents="true"
+            android:theme="@*android:style/Theme.Holo.Dialog.Alert">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <receiver android:name=".BluetoothConnectionReceiver" >
+            <intent-filter>
+                <action android:name="org.codeaurora.bluetooth.action.NEW_BLUETOOTH_DEVICE" />
+                <action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
+            </intent-filter>
+        </receiver>
+
+    </application>
+
+</manifest>
diff --git a/bttestapp/res/drawable-hdpi/ic_bt_connected.png b/bttestapp/res/drawable-hdpi/ic_bt_connected.png
new file mode 100644
index 0000000..94c0ffc
--- /dev/null
+++ b/bttestapp/res/drawable-hdpi/ic_bt_connected.png
Binary files differ
diff --git a/bttestapp/res/drawable-hdpi/ic_bt_disconnected.png b/bttestapp/res/drawable-hdpi/ic_bt_disconnected.png
new file mode 100644
index 0000000..e7689b3
--- /dev/null
+++ b/bttestapp/res/drawable-hdpi/ic_bt_disconnected.png
Binary files differ
diff --git a/bttestapp/res/drawable-hdpi/ic_call_incoming_holo_dark.png b/bttestapp/res/drawable-hdpi/ic_call_incoming_holo_dark.png
new file mode 100644
index 0000000..aaa28d0
--- /dev/null
+++ b/bttestapp/res/drawable-hdpi/ic_call_incoming_holo_dark.png
Binary files differ
diff --git a/bttestapp/res/drawable-hdpi/ic_call_outgoing_holo_dark.png b/bttestapp/res/drawable-hdpi/ic_call_outgoing_holo_dark.png
new file mode 100644
index 0000000..f52677a
--- /dev/null
+++ b/bttestapp/res/drawable-hdpi/ic_call_outgoing_holo_dark.png
Binary files differ
diff --git a/bttestapp/res/drawable-hdpi/ic_contact_picture.png b/bttestapp/res/drawable-hdpi/ic_contact_picture.png
new file mode 100644
index 0000000..0997e31
--- /dev/null
+++ b/bttestapp/res/drawable-hdpi/ic_contact_picture.png
Binary files differ
diff --git a/bttestapp/res/drawable-hdpi/ic_launcher.png b/bttestapp/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f83649f
--- /dev/null
+++ b/bttestapp/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/bttestapp/res/drawable-hdpi/ic_menu_save.png b/bttestapp/res/drawable-hdpi/ic_menu_save.png
new file mode 100644
index 0000000..306d55a
--- /dev/null
+++ b/bttestapp/res/drawable-hdpi/ic_menu_save.png
Binary files differ
diff --git a/bttestapp/res/drawable-hdpi/sym_keyboard_delete.png b/bttestapp/res/drawable-hdpi/sym_keyboard_delete.png
new file mode 100644
index 0000000..77cdf75
--- /dev/null
+++ b/bttestapp/res/drawable-hdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/bttestapp/res/drawable-mdpi/ic_bt_connected.png b/bttestapp/res/drawable-mdpi/ic_bt_connected.png
new file mode 100644
index 0000000..da98b1b
--- /dev/null
+++ b/bttestapp/res/drawable-mdpi/ic_bt_connected.png
Binary files differ
diff --git a/bttestapp/res/drawable-mdpi/ic_bt_disconnected.png b/bttestapp/res/drawable-mdpi/ic_bt_disconnected.png
new file mode 100644
index 0000000..c8eeeee
--- /dev/null
+++ b/bttestapp/res/drawable-mdpi/ic_bt_disconnected.png
Binary files differ
diff --git a/bttestapp/res/drawable-mdpi/ic_call_incoming_holo_dark.png b/bttestapp/res/drawable-mdpi/ic_call_incoming_holo_dark.png
new file mode 100644
index 0000000..98678c9
--- /dev/null
+++ b/bttestapp/res/drawable-mdpi/ic_call_incoming_holo_dark.png
Binary files differ
diff --git a/bttestapp/res/drawable-mdpi/ic_call_outgoing_holo_dark.png b/bttestapp/res/drawable-mdpi/ic_call_outgoing_holo_dark.png
new file mode 100644
index 0000000..bbe177c
--- /dev/null
+++ b/bttestapp/res/drawable-mdpi/ic_call_outgoing_holo_dark.png
Binary files differ
diff --git a/bttestapp/res/drawable-mdpi/ic_contact_picture.png b/bttestapp/res/drawable-mdpi/ic_contact_picture.png
new file mode 100644
index 0000000..b1f8be1
--- /dev/null
+++ b/bttestapp/res/drawable-mdpi/ic_contact_picture.png
Binary files differ
diff --git a/bttestapp/res/drawable-mdpi/ic_launcher.png b/bttestapp/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..8c4edba
--- /dev/null
+++ b/bttestapp/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/bttestapp/res/drawable-mdpi/ic_menu_save.png b/bttestapp/res/drawable-mdpi/ic_menu_save.png
new file mode 100644
index 0000000..ad07695
--- /dev/null
+++ b/bttestapp/res/drawable-mdpi/ic_menu_save.png
Binary files differ
diff --git a/bttestapp/res/drawable-mdpi/sym_keyboard_delete.png b/bttestapp/res/drawable-mdpi/sym_keyboard_delete.png
new file mode 100644
index 0000000..761e7ba
--- /dev/null
+++ b/bttestapp/res/drawable-mdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/bttestapp/res/drawable-xhdpi/ic_bt_connected.png b/bttestapp/res/drawable-xhdpi/ic_bt_connected.png
new file mode 100644
index 0000000..22f1f5f
--- /dev/null
+++ b/bttestapp/res/drawable-xhdpi/ic_bt_connected.png
Binary files differ
diff --git a/bttestapp/res/drawable-xhdpi/ic_bt_disconnected.png b/bttestapp/res/drawable-xhdpi/ic_bt_disconnected.png
new file mode 100644
index 0000000..569f2ba
--- /dev/null
+++ b/bttestapp/res/drawable-xhdpi/ic_bt_disconnected.png
Binary files differ
diff --git a/bttestapp/res/drawable-xhdpi/ic_call_incoming_holo_dark.png b/bttestapp/res/drawable-xhdpi/ic_call_incoming_holo_dark.png
new file mode 100644
index 0000000..d787229
--- /dev/null
+++ b/bttestapp/res/drawable-xhdpi/ic_call_incoming_holo_dark.png
Binary files differ
diff --git a/bttestapp/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png b/bttestapp/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png
new file mode 100644
index 0000000..ada5cf8
--- /dev/null
+++ b/bttestapp/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png
Binary files differ
diff --git a/bttestapp/res/drawable-xhdpi/ic_contact_picture.png b/bttestapp/res/drawable-xhdpi/ic_contact_picture.png
new file mode 100644
index 0000000..46ef148
--- /dev/null
+++ b/bttestapp/res/drawable-xhdpi/ic_contact_picture.png
Binary files differ
diff --git a/bttestapp/res/drawable-xhdpi/ic_launcher.png b/bttestapp/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..b4d5d6c
--- /dev/null
+++ b/bttestapp/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/bttestapp/res/drawable-xhdpi/ic_menu_save.png b/bttestapp/res/drawable-xhdpi/ic_menu_save.png
new file mode 100644
index 0000000..2720602
--- /dev/null
+++ b/bttestapp/res/drawable-xhdpi/ic_menu_save.png
Binary files differ
diff --git a/bttestapp/res/drawable-xhdpi/sym_keyboard_delete.png b/bttestapp/res/drawable-xhdpi/sym_keyboard_delete.png
new file mode 100644
index 0000000..384315e
--- /dev/null
+++ b/bttestapp/res/drawable-xhdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/bttestapp/res/drawable/selected_call_bg.xml b/bttestapp/res/drawable/selected_call_bg.xml
new file mode 100644
index 0000000..bd0d805
--- /dev/null
+++ b/bttestapp/res/drawable/selected_call_bg.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/call_selected" />
+    <stroke android:width="3dip" android:color="#800000" />
+
+</shape>
diff --git a/bttestapp/res/layout/activity_hfp_test.xml b/bttestapp/res/layout/activity_hfp_test.xml
new file mode 100644
index 0000000..5c3071a
--- /dev/null
+++ b/bttestapp/res/layout/activity_hfp_test.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:layout_margin="5dip"
+    android:baselineAligned="false">
+
+    <LinearLayout
+        android:id="@+id/hfptest_left_panel"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="35"
+        android:orientation="vertical"
+        android:paddingRight="10dip">
+
+        <fragment class="org.codeaurora.bluetooth.bttestapp.IndicatorsFragment"
+            android:id="@+id/indicators"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+        <fragment class="org.codeaurora.bluetooth.bttestapp.DialpadFragment"
+            android:id="@+id/dialpad"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+    <fragment class="org.codeaurora.bluetooth.bttestapp.CallsListFragment"
+        android:id="@+id/calls_list"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="65" />
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/activity_main.xml b/bttestapp/res/layout/activity_main.xml
new file mode 100644
index 0000000..edfdd60
--- /dev/null
+++ b/bttestapp/res/layout/activity_main.xml
@@ -0,0 +1,107 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    tools:context=".MainActivity" >
+
+    <RelativeLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="16dp" >
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:layout_alignParentLeft="true" >
+
+            <TextView
+                android:id="@+id/device_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/blank"
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <TextView
+                android:id="@+id/device_address"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/blank"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:gravity="center"
+            android:layout_alignParentRight="true" >
+
+            <Button
+                android:id="@+id/discover_services"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/discover_services"
+                android:onClick="onButtonClick" />
+
+            <Button
+                android:id="@+id/select_device"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/select_device"
+                android:onClick="onButtonClick" />
+
+        </LinearLayout>
+
+    </RelativeLayout>
+
+    <TextView
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:text="@string/services_header"
+            style="?android:attr/listSeparatorTextViewStyle" />
+
+    <fragment class="org.codeaurora.bluetooth.bttestapp.ServicesFragment"
+            android:id="@+id/services_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:padding="16dp" />
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/activity_map_test.xml b/bttestapp/res/layout/activity_map_test.xml
new file mode 100644
index 0000000..351a860
--- /dev/null
+++ b/bttestapp/res/layout/activity_map_test.xml
@@ -0,0 +1,682 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="#101010" >
+
+<LinearLayout
+    android:id="@+id/maptest_folder_nav"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="#003000"
+    style="@style/somePadding" >
+
+    <Button
+        android:id="@+id/maptest_nav_root"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:text="@string/map_folder_root"
+        android:onClick="onClickSetPathRoot" />
+
+    <Button
+        android:id="@+id/maptest_nav_up"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:text="@string/map_folder_up"
+        android:onClick="onClickSetPathUp" />
+
+    <TextView
+        android:id="@+id/maptest_nav_current"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_marginLeft="20dp"
+        android:layout_marginRight="10dp"
+        android:gravity="center_vertical|right"
+        android:text="@string/blank" />
+
+    <Spinner
+        android:id="@+id/maptest_nav_folders"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_weight="1" />
+
+    <Button
+        android:id="@+id/maptest_nav_down"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_weight="0"
+        android:text="@string/map_folder_enter"
+        android:onClick="onClickSetPathEnter" />
+
+    <EditText
+        android:layout_marginLeft="40dp"
+        android:id="@+id/maptest_nav_list_max"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:ems="2"
+        android:inputType="number" />
+
+    <EditText
+        android:id="@+id/maptest_nav_list_offset"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:ems="2"
+        android:inputType="number" />
+
+    <Button
+        android:id="@+id/maptest_nav_list_get"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_weight="0"
+        android:text="@string/map_folder_listing"
+        android:onClick="onClickGetFolderListing" />
+
+    <Button
+        android:id="@+id/maptest_nav_list_size"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_weight="0"
+        android:text="@string/map_folder_listing_size"
+        android:onClick="onClickGetFolderListingSize" />
+
+</LinearLayout>
+
+<ViewFlipper
+    android:id="@+id/maptest_viewflipper"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+<!-- LIST TAB -->
+<LinearLayout
+    android:id="@+id/maptest_tab_list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal" >
+
+    <!-- Browsing Layout -->
+    <LinearLayout
+        android:layout_width="250dp"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:background="#000030" >
+
+        <!-- Horizontal line -->
+        <View
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+
+            <Button
+                android:id="@+id/maptest_msglist_params"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/map_message_parameters"
+                android:onClick="onClickMessageParameters" />
+
+            <Button
+                android:id="@+id/maptest_msglist_filter"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/map_message_filter"
+                android:onClick="onClickGetMessagesFilter" />
+
+        </LinearLayout>
+
+        <TableLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:focusableInTouchMode="true" >
+
+            <!-- MaxListCount -->
+            <TableRow>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/map_max_list_count_prompt" />
+
+                <EditText
+                    android:id="@+id/maptest_msglist_max"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:inputType="number" />
+            </TableRow>
+
+            <!-- ListStartOffset -->
+            <TableRow>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/map_list_start_offset_prompt" />
+
+                <EditText
+                    android:id="@+id/maptest_msglist_offset"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:inputType="number" />
+            </TableRow>
+
+            <!-- SubjectLength -->
+            <TableRow>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/map_subject_length_prompt" />
+
+                <EditText
+                    android:id="@+id/maptest_msglist_subject_len"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:inputType="number" />
+            </TableRow>
+
+        </TableLayout>
+
+        <Button
+            android:id="@+id/maptest_msglist_get"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/map_message_listing"
+            android:onClick="onClickGetMessagesListing" />
+
+        <Button
+            android:id="@+id/maptest_msglist_get_size"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/map_message_listing_size"
+            android:onClick="onClickGetMessageListingSize" />
+
+    </LinearLayout>
+
+    <!-- Messages layout -->
+    <ListView
+        android:id="@+id/maptest_msglist_lv"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_weight="1"
+        android:orientation="vertical" >
+
+        <ProgressBar
+            android:id="@+id/maptest_msglist_progressbar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/progressBarStyleLarge"
+            android:visibility="gone" />
+
+        <TextView
+            android:id="@+id/maptest_msglist_empty"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_messages_list_empty"
+            android:layout_gravity="center_horizontal|center_vertical"
+            android:visibility="gone" />
+
+    </LinearLayout>
+
+</LinearLayout>
+
+<!-- PREVIEW TAB -->
+<LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="#000030"
+        style="@style/somePadding" >
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:orientation="horizontal" >
+            <Button
+                android:id="@+id/map_msg_get"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_msg_get_lbl"
+                android:onClick="onClickGetMessage" />
+            <EditText
+                android:id="@+id/map_msg_handle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ems="10" />
+            <RadioGroup
+                android:id="@+id/map_msg_get_charset"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:orientation="horizontal" >
+                <RadioButton
+                    android:id="@+id/map_msg_get_charset_native"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:text="@string/charset_native_lbl" />
+                <RadioButton
+                    android:id="@+id/map_msg_get_charset_utf8"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:text="@string/charset_utf8_lbl"
+                    android:checked="true" />
+            </RadioGroup>
+            <CheckBox
+                android:id="@+id/map_msg_get_attachment"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_msg_get_attachment_lbl" />
+        </LinearLayout>
+
+        <Button
+            android:id="@+id/map_msg_copy_to_editor"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:text="@string/map_msg_copy_to_editor_lbl"
+            android:onClick="onClickCopyMessage" />
+        <Button
+            android:id="@+id/map_msg_delete"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_delete_message"
+            android:layout_toLeftOf="@id/map_msg_copy_to_editor"
+            android:onClick="onClickDeleteMessage" />
+        <Button
+            android:id="@+id/map_msg_set_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_set_unread"
+            android:layout_toLeftOf="@id/map_msg_delete"
+            android:onClick="onClickSetStatus" />
+    </RelativeLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        style="@style/somePadding" >
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_bmsg_status_prompt" />
+            <EditText
+                android:id="@+id/maptest_bmsg_status"
+                android:editable="false"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_bmsg_type_prompt" />
+            <EditText
+                android:id="@+id/maptest_bmsg_type"
+                android:editable="false"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="3">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_bmsg_folder_prompt" />
+            <EditText
+                android:id="@+id/maptest_bmsg_folder"
+                android:editable="false"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        style="@style/somePadding" >
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_bbody_encoding_prompt" />
+            <EditText
+                android:id="@+id/maptest_bbody_encoding"
+                android:editable="false"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_bbody_charset_prompt" />
+            <EditText
+                android:id="@+id/maptest_bbody_charset"
+                android:editable="false"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_bbody_language_prompt" />
+            <EditText
+                android:id="@+id/maptest_bbody_language"
+                android:editable="false"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="#303030"
+        style="@style/somePadding" >
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_originators_prompt" />
+        <EditText
+            android:id="@+id/maptest_orig"
+            android:editable="false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="#303030"
+        style="@style/somePadding" >
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_recipients_prompt" />
+        <EditText
+            android:id="@+id/maptest_rcpt"
+            android:editable="false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <EditText
+        android:id="@+id/maptest_message"
+        android:editable="false"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:typeface="monospace"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        style="@style/somePadding" />
+
+</LinearLayout>
+
+
+<LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="#000030"
+        style="@style/somePadding" >
+
+        <Button
+            android:id="@+id/map_msg_push"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_msg_push_lbl"
+            android:onClick="onClickPushMessage" />
+        <CheckBox
+            android:id="@+id/map_msg_push_transparent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_msg_push_transparent_lbl" />
+        <CheckBox
+            android:id="@+id/map_msg_push_retry"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_msg_push_retry_lbl" />
+        <RadioGroup
+            android:id="@+id/map_msg_push_charset"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:orientation="horizontal" >
+            <RadioButton
+                android:id="@+id/map_msg_push_charset_native"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:text="@string/charset_native_lbl" />
+            <RadioButton
+                android:id="@+id/map_msg_push_charset_utf8"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:text="@string/charset_utf8_lbl"
+                android:checked="true" />
+        </RadioGroup>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        style="@style/somePadding" >
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_bmsg_type_prompt" />
+            <Spinner
+                android:id="@+id/bmsgedit_type"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:entries="@array/map_message_type_array" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_bbody_encoding_prompt" />
+            <Spinner
+                android:id="@+id/bmsgedit_encoding"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:entries="@array/map_message_encoding_array" />
+        </LinearLayout>
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="3" />
+
+    </LinearLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="#303030"
+        style="@style/somePadding" >
+
+        <TextView
+            android:id="@+id/bmsgedit_orig_add_lbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:text="@string/map_originators_prompt" />
+        <Button
+            android:id="@+id/bmsgedit_orig_del"
+            android:text="@string/ppl_del"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:ems="1"
+            android:onClick="onClickRemovePeople" />
+        <Button
+            android:id="@+id/bmsgedit_orig_add"
+            android:text="@string/ppl_add"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toLeftOf="@id/bmsgedit_orig_del"
+            android:ems="1"
+            android:onClick="onClickAddPerson" />
+        <EditText
+            android:id="@+id/bmsgedit_orig"
+            android:editable="false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_toLeftOf="@id/bmsgedit_orig_add"
+            android:layout_toRightOf="@id/bmsgedit_orig_add_lbl" />
+
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="#303030"
+        style="@style/somePadding" >
+
+        <TextView
+            android:id="@+id/bmsgedit_rcpt_add_lbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:text="@string/map_recipients_prompt" />
+        <Button
+            android:id="@+id/bmsgedit_rcpt_del"
+            android:text="@string/ppl_del"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:ems="1"
+            android:onClick="onClickRemovePeople" />
+        <Button
+            android:id="@+id/bmsgedit_rcpt_add"
+            android:text="@string/ppl_add"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toLeftOf="@id/bmsgedit_rcpt_del"
+            android:ems="1"
+            android:onClick="onClickAddPerson" />
+        <EditText
+            android:id="@+id/bmsgedit_rcpt"
+            android:editable="false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_toLeftOf="@id/bmsgedit_rcpt_add"
+            android:layout_toRightOf="@id/bmsgedit_rcpt_add_lbl" />
+
+    </RelativeLayout>
+
+    <EditText
+        android:id="@+id/bmsgedit_contents"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:typeface="monospace"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        style="@style/somePadding" />
+
+</LinearLayout>
+
+</ViewFlipper>
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/activity_message_filter.xml b/bttestapp/res/layout/activity_message_filter.xml
new file mode 100644
index 0000000..6f0334c
--- /dev/null
+++ b/bttestapp/res/layout/activity_message_filter.xml
@@ -0,0 +1,147 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal" >
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="vertical" >
+
+        <CheckBox
+            android:id="@+id/param_subject"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_subject" />
+
+        <CheckBox
+            android:id="@+id/param_datatime"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_datetime" />
+
+        <CheckBox
+            android:id="@+id/param_sender_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_sender_name" />
+
+        <CheckBox
+            android:id="@+id/param_sender_addressing"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_sender_addressing" />
+
+        <CheckBox
+            android:id="@+id/param_receipent_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_recipient_name" />
+
+        <CheckBox
+            android:id="@+id/param_receipent_addressing"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_recipient_addressing" />
+
+        <CheckBox
+            android:id="@+id/param_type"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_type" />
+
+        <CheckBox
+            android:id="@+id/param_size"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_size" />
+
+        <CheckBox
+            android:id="@+id/param_reception_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_reception_status" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="vertical" >
+
+         <CheckBox
+            android:id="@+id/param_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_text" />
+
+        <CheckBox
+            android:id="@+id/param_attachment_size"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_attachment_size" />
+
+        <CheckBox
+            android:id="@+id/param_priority"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_priority" />
+
+        <CheckBox
+            android:id="@+id/param_read"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_read" />
+
+        <CheckBox
+            android:id="@+id/param_sent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_sent" />
+
+        <CheckBox
+            android:id="@+id/param_protected"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_protected" />
+
+        <CheckBox
+            android:id="@+id/param_replayto_addressing"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/map_parameter_replyto_addressing" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/activity_pbap_test.xml b/bttestapp/res/layout/activity_pbap_test.xml
new file mode 100644
index 0000000..87561ec
--- /dev/null
+++ b/bttestapp/res/layout/activity_pbap_test.xml
@@ -0,0 +1,561 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<ViewFlipper xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/pbap_test_viewflipper"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" >
+
+<RelativeLayout
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="horizontal" >
+
+        <RelativeLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:orientation="vertical" >
+
+            <TextView
+                android:id="@+id/label_download_phonebook_prompt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentTop="true"
+                android:text="@string/phonebook_prompt" />
+
+            <Spinner
+                android:id="@+id/pbap_download_spinner"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:entries="@array/phonebook_arrays"
+                android:prompt="@string/phonebook_prompt"
+                android:layout_alignParentLeft="true"
+                android:layout_below="@+id/label_download_phonebook_prompt"/>
+
+            <TextView
+                android:id="@+id/label_download_format_prompt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/pbap_download_spinner"
+                android:layout_marginTop="5dp"
+                android:text="@string/vcard_format_prompt" />
+
+            <RadioGroup
+                android:id="@+id/pbap_download_formats"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/label_download_format_prompt"
+                android:orientation="horizontal" >
+
+                <RadioButton
+                    android:id="@+id/pbap_download_vcard21"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/vcard21"
+                    android:checked="true" />
+
+                <RadioButton
+                    android:id="@+id/pbap_download_vcard30"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/vcard30" />
+            </RadioGroup>
+
+            <LinearLayout
+                android:id="@+id/pbap_layout_1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/pbap_download_formats"
+                android:layout_marginTop="5dp" >
+                <TextView
+                    android:id="@+id/label_download_list_count_prompt"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/max_list_count_prompt"
+                    android:layout_weight="1.0" />
+                <TextView
+                    android:id="@+id/label_download_offset_prompt"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/offset_prompt"
+                    android:layout_weight="1.0" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/pbap_layout_2"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/pbap_layout_1"
+                android:layout_marginTop="5dp" >
+                <EditText
+                    android:id="@+id/pbap_download_max_list_count"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:ems="10"
+                    android:inputType="number"
+                    android:layout_weight="1.0" />
+                <EditText
+                    android:id="@+id/pbap_download_offset_value"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:ems="10"
+                    android:inputType="number"
+                    android:layout_weight="1.0" />
+            </LinearLayout>
+
+            <Button
+                android:id="@+id/pbap_download_filter_button"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_button"
+                android:layout_below="@+id/pbap_layout_2"
+                android:layout_marginTop="5dp" />
+
+            <Button
+                android:id="@+id/pbap_download_search"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentRight="true"
+                android:text="@string/search"
+                android:layout_marginBottom="15dp"
+                android:layout_marginRight="15dp" />
+
+            <Button
+                android:id="@+id/pbap_download_abort"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:layout_toLeftOf="@id/pbap_download_search"
+                android:text="@string/abort"
+                android:layout_marginBottom="15dp"
+                android:layout_marginRight="15dp"
+                android:onClick="onClick_abort" />
+
+            <Button
+                android:id="@+id/pbap_download_getsize"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:layout_toLeftOf="@id/pbap_download_abort"
+                android:text="@string/getsize"
+                android:layout_marginBottom="15dp"
+                android:layout_marginRight="15dp"
+                android:onClick="onClick_download_getsize" />
+
+        </RelativeLayout>
+
+        <LinearLayout
+            android:id="@+id/pbap_download_layout_listview"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_weight="1"
+            android:orientation="vertical" >
+            <ListView android:id="@+id/pbap_download_listview_contacts_list"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <ProgressBar
+                android:id="@+id/pbap_download_progress_bar"
+                style="?android:attr/progressBarStyleLarge"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|center_horizontal" />
+
+            <TextView
+                android:id="@+id/pbap_download_test_list_empty"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:text="@string/list_empty" />
+        </LinearLayout>
+    </LinearLayout>
+</RelativeLayout>
+
+<RelativeLayout
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="horizontal" >
+
+        <RelativeLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1"
+            android:orientation="vertical" >
+
+            <TextView
+                android:id="@+id/label_browse_phonebook_prompt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentTop="true"
+                android:text="@string/phonebook_prompt" />
+
+             <LinearLayout
+                android:id="@+id/pbap_browse_pb_group"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:layout_below="@+id/label_browse_phonebook_prompt" >
+
+                 <Spinner
+                     android:id="@+id/pbap_browse_spinner"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_weight="1"
+                     android:entries="@array/phonebook_arrays"
+                     android:prompt="@string/phonebook_prompt" />
+
+                 <Button
+                     android:id="@+id/pbap_browse_set_phonebook"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_weight="1"
+                     android:text="@string/set_phonebook"
+                     android:layout_marginRight="15dp"
+                     android:tag="pbap_browse_spinner"
+                     android:onClick="onClick_setPhonebook" />
+             </LinearLayout>
+
+
+            <TextView
+                android:id="@+id/label_search_attribute_prompt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/pbap_browse_pb_group"
+                android:layout_marginTop="5dp"
+                android:text="@string/search_attribute_prompt" />
+
+            <RadioGroup
+                android:id="@+id/search_attributes"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/label_search_attribute_prompt"
+                android:orientation="horizontal" >
+
+                <RadioButton
+                    android:id="@+id/pbap_browse_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/search_attribute_name"
+                    android:checked="true" />
+
+                <RadioButton
+                    android:id="@+id/pbap_browse_number"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/search_attribute_number" />
+
+                <RadioButton
+                    android:id="@+id/pbap_browse_sound"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/search_attribute_sound" />
+            </RadioGroup>
+
+            <TextView
+                android:id="@+id/label_search_value"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/search_attributes"
+                android:layout_marginTop="5dp"
+                android:text="@string/search_value_prompt" />
+
+            <EditText
+                android:id="@+id/pbap_browse_search_value"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/label_search_value"
+                android:ems="10" />
+
+            <LinearLayout
+                android:id="@+id/pbap_layout_3"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/pbap_browse_search_value"
+                android:layout_marginTop="5dp" >
+                <TextView
+                    android:id="@+id/label_browse_list_count_prompt"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/max_list_count_prompt"
+                    android:layout_weight="1.0" />
+                <TextView
+                    android:id="@+id/label_browse_offset_prompt"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/offset_prompt"
+                    android:layout_weight="1.0" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/pbap_layout_4"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/pbap_layout_3"
+                android:layout_marginTop="5dp" >
+                <EditText
+                    android:id="@+id/pbap_browse_max_list_count"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:ems="10"
+                    android:inputType="number"
+                    android:layout_weight="1.0" />
+                <EditText
+                    android:id="@+id/pbap_browse_offset_value"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:ems="10"
+                    android:inputType="number"
+                    android:layout_weight="1.0" />
+            </LinearLayout>
+
+            <RadioGroup
+                android:id="@+id/order_attribute"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/pbap_layout_4"
+                android:orientation="horizontal" >
+
+                <RadioButton
+                    android:id="@+id/pbap_browse_unordered"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/order_attribute_unordered"
+                    android:checked="true" />
+
+                <RadioButton
+                    android:id="@+id/pbap_browse_alphabetical"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/order_attribute_alphabetical" />
+
+                <RadioButton
+                    android:id="@+id/pbap_browse_indexed"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/order_attribute_indexed" />
+
+                <RadioButton
+                    android:id="@+id/pbap_browse_phonetic"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/order_attribute_phonetic" />
+            </RadioGroup>
+
+            <Button
+                android:id="@+id/pbap_browse_search"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentRight="true"
+                android:text="@string/search"
+                android:layout_marginBottom="15dp"
+                android:layout_marginRight="15dp" />
+
+
+            <Button
+                android:id="@+id/pbap_browse_abort"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:layout_toLeftOf="@id/pbap_browse_search"
+                android:text="@string/abort"
+                android:layout_marginBottom="15dp"
+                android:layout_marginRight="15dp"
+                android:onClick="onClick_abort" />
+
+            <Button
+                android:id="@+id/pbap_browse_getsize"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:layout_toLeftOf="@id/pbap_browse_abort"
+                android:text="@string/getsize"
+                android:layout_marginBottom="15dp"
+                android:layout_marginRight="15dp"
+                android:onClick="onClick_browse_getsize" />
+
+        </RelativeLayout>
+
+        <LinearLayout
+            android:id="@+id/pbap_browse_layout_listview"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_weight="1"
+            android:orientation="vertical" >
+            <ListView android:id="@+id/pbap_browse_listview_contacts_list"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <ProgressBar
+                android:id="@+id/pbap_browse_progress_bar"
+                style="?android:attr/progressBarStyleLarge"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|center_horizontal" />
+
+            <TextView
+                android:id="@+id/pbap_browse_test_list_empty"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:text="@string/list_empty" />
+        </LinearLayout>
+    </LinearLayout>
+</RelativeLayout>
+
+<RelativeLayout
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="horizontal" >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:layout_weight="1" >
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/phonebook_prompt" />
+
+             <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal" >
+
+                 <Spinner
+                     android:id="@+id/pbap_vcard_spinner"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_weight="1"
+                     android:entries="@array/phonebook_arrays"
+                     android:prompt="@string/phonebook_prompt" />
+
+                 <Button
+                     android:id="@+id/pbap_vcard_set_phonebook"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_weight="1"
+                     android:text="@string/set_phonebook"
+                     android:layout_marginRight="15dp"
+                     android:tag="pbap_vcard_spinner"
+                     android:onClick="onClick_setPhonebook" />
+             </LinearLayout>
+
+             <TextView
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:text="@string/vcard_header_prompt" />
+
+             <EditText
+                 android:id="@+id/pbap_vcard_header"
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:ems="10"
+                 android:inputType="text" />
+
+             <TextView
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:layout_marginTop="5dp"
+                 android:text="@string/vcard_format_prompt" />
+
+             <RadioGroup
+                 android:id="@+id/pbab_vcard_formats"
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:orientation="horizontal" >
+
+                 <RadioButton
+                     android:id="@+id/pbap_vcard_vcard21"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:text="@string/vcard21"
+                     android:checked="true"
+                     android:onClick="onClickRadioButtonVcardType21" />
+
+                 <RadioButton
+                     android:id="@+id/pbap_vcard_vcard30"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:text="@string/vcard30"
+                     android:onClick="onClickRadioButtonVcardType30" />
+             </RadioGroup>
+
+             <Button
+                 android:id="@+id/pbap_vcard_filter_button"
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:text="@string/filter_button"
+                 android:layout_marginTop="5dp"
+                 android:onClick="onClickVcardFilterAttributes"/>
+
+             <Button
+                 android:id="@+id/pbap_vcard_get_vcard"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_gravity="right"
+                 android:layout_marginTop="50dp"
+                 android:text="@string/get_vcard"
+                 android:onClick="onClickVcardButtonGet" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1" >
+
+            <org.codeaurora.bluetooth.bttestapp.VcardView
+                android:id="@+id/pbap_vcard_vcardview"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" />
+
+        </LinearLayout>
+    </LinearLayout>
+</RelativeLayout>
+</ViewFlipper>
diff --git a/bttestapp/res/layout/activity_vcard_filter.xml b/bttestapp/res/layout/activity_vcard_filter.xml
new file mode 100644
index 0000000..8b41dcd
--- /dev/null
+++ b/bttestapp/res/layout/activity_vcard_filter.xml
@@ -0,0 +1,202 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" >
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:baselineAligned="false" >
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical" >
+            <CheckBox
+                android:id="@+id/filter_chooser_version"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_version" />
+            <CheckBox
+                android:id="@+id/filter_chooser_fn"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_fn" />
+            <CheckBox
+                android:id="@+id/filter_chooser_n"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_n" />
+            <CheckBox
+                android:id="@+id/filter_chooser_photo"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_photo" />
+            <CheckBox
+                android:id="@+id/filter_chooser_bday"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_bday" />
+            <CheckBox
+                android:id="@+id/filter_chooser_adr"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_adr" />
+            <CheckBox
+                android:id="@+id/filter_chooser_label"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_label" />
+            <CheckBox
+                android:id="@+id/filter_chooser_tel"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_tel" />
+            <CheckBox
+                android:id="@+id/filter_chooser_email"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_email" />
+            <CheckBox
+                android:id="@+id/filter_chooser_mailer"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_mailer" />
+            <CheckBox
+                android:id="@+id/filter_chooser_tz"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_tz" />
+            <CheckBox
+                android:id="@+id/filter_chooser_geo"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_geo" />
+            <CheckBox
+                android:id="@+id/filter_chooser_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_title" />
+            <CheckBox
+                android:id="@+id/filter_chooser_role"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_role" />
+            <CheckBox
+                android:id="@+id/filter_chooser_logo"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_logo" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical">
+            <CheckBox
+                android:id="@+id/filter_chooser_agent"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_agent" />
+            <CheckBox
+                android:id="@+id/filter_chooser_org"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_org" />
+            <CheckBox
+                android:id="@+id/filter_chooser_note"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_note" />
+            <CheckBox
+                android:id="@+id/filter_chooser_rev"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_rev" />
+            <CheckBox
+                android:id="@+id/filter_chooser_sound"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_sound" />
+            <CheckBox
+                android:id="@+id/filter_chooser_url"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_url" />
+            <CheckBox
+                android:id="@+id/filter_chooser_uid"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_uid" />
+            <CheckBox
+                android:id="@+id/filter_chooser_key"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_key" />
+            <CheckBox
+                android:id="@+id/filter_chooser_nickname"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_nickname" />
+            <CheckBox
+                android:id="@+id/filter_chooser_categories"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_categories" />
+            <CheckBox
+                android:id="@+id/filter_chooser_proid"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_proid" />
+            <CheckBox
+                android:id="@+id/filter_chooser_class"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_class" />
+            <CheckBox
+                android:id="@+id/filter_chooser_sort_string"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_sort_string" />
+            <CheckBox
+                android:id="@+id/filter_chooser_x_irmc_call_datetime"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_x_irmc_call_datetime" />
+            <CheckBox
+                android:id="@+id/filter_chooser_filter"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/filter_field_filter" />
+        </LinearLayout>
+    </LinearLayout>
+</ScrollView>
diff --git a/bttestapp/res/layout/call_view.xml b/bttestapp/res/layout/call_view.xml
new file mode 100644
index 0000000..a415f41
--- /dev/null
+++ b/bttestapp/res/layout/call_view.xml
@@ -0,0 +1,182 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:orientation="horizontal" >
+
+    <TextView
+        android:id="@+id/call_id"
+        android:layout_width="50dip"
+        android:layout_height="50dip"
+        android:layout_marginTop="5dp"
+        android:layout_marginBottom="5dp"
+        android:layout_marginLeft="5dp"
+        android:layout_marginRight="10dp"
+        android:gravity="center|center_vertical"
+        android:background="@color/call_unknown"
+        android:textAppearance="?android:attr/textAppearanceLarge" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight=".4"
+        android:orientation="vertical" >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables" >
+
+            <ImageView
+                android:id="@+id/call_direction"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:layout_marginRight="10dp"
+                tools:ignore="ContentDescription"/>
+
+            <TextView
+                android:id="@+id/call_number"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/call_multiparty"
+            style="@style/indicator"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="5dip"
+            android:paddingRight="5dip"
+            android:background="@color/ind_off"
+            android:text="@string/call_multiparty" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight=".6"
+        android:orientation="vertical" >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+
+            <TextView
+                style="@style/indicator"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:visibility="invisible" />
+
+            <TextView
+                android:id="@+id/call_state_active"
+                style="@style/indicator"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@color/call_active"
+                android:text="@string/call_state_active" />
+
+            <TextView
+                android:id="@+id/call_state_held"
+                style="@style/indicator"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@color/call_held"
+                android:text="@string/call_state_held" />
+
+            <TextView
+                android:id="@+id/call_state_held_by_rnh"
+                style="@style/indicator"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@color/call_held"
+                android:text="@string/call_state_held_by_rnh" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+
+            <TextView
+                android:id="@+id/call_state_dialing"
+                style="@style/indicator"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@color/call_new"
+                android:text="@string/call_state_dialing" />
+
+            <TextView
+                android:id="@+id/call_state_alerting"
+                style="@style/indicator"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@color/call_new"
+                android:text="@string/call_state_alerting" />
+
+            <TextView
+                android:id="@+id/call_state_incoming"
+                style="@style/indicator"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@color/call_new"
+                android:text="@string/call_state_incoming" />
+
+            <TextView
+                android:id="@+id/call_state_waiting"
+                style="@style/indicator"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@color/call_new"
+                android:text="@string/call_state_waiting" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/calls_list_fragment.xml b/bttestapp/res/layout/calls_list_fragment.xml
new file mode 100644
index 0000000..a3f3eb2
--- /dev/null
+++ b/bttestapp/res/layout/calls_list_fragment.xml
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <ListView
+        android:id="@+id/calls_list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/call_action_accept"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:text="@string/call_action_accept" />
+
+            <Button
+                android:id="@+id/call_action_hold_and_accept"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:text="@string/call_action_hold_and_accept" />
+
+            <Button
+                android:id="@+id/call_action_release_and_accept"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:text="@string/call_action_release_and_accept" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/call_action_reject"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:text="@string/call_action_reject" />
+
+            <Button
+                android:id="@+id/call_action_terminate"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:text="@string/call_action_terminate" />
+
+            <Button
+                android:id="@+id/call_action_hold"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:text="@string/call_action_hold" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/call_action_respond_and_hold"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:text="@string/call_action_respond_and_hold" />
+
+            <Button
+                android:id="@+id/call_action_private_mode"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:text="@string/call_action_private_mode" />
+
+            <Button
+                android:id="@+id/call_action_explicit_transfer"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:text="@string/call_action_explicit_transfer" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/date_time_picker.xml b/bttestapp/res/layout/date_time_picker.xml
new file mode 100644
index 0000000..ea567aa
--- /dev/null
+++ b/bttestapp/res/layout/date_time_picker.xml
@@ -0,0 +1,46 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal" >
+
+    <DatePicker
+        android:id="@+id/date_picker"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:calendarViewShown="false" />
+
+    <TimePicker
+        android:id="@+id/time_picker"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/dialpad_fragment.xml b/bttestapp/res/layout/dialpad_fragment.xml
new file mode 100644
index 0000000..ae33855
--- /dev/null
+++ b/bttestapp/res/layout/dialpad_fragment.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <EditText
+            android:id="@+id/dialpad_number"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:editable="false"
+            android:singleLine="true"
+            android:ellipsize="start"
+            android:scrollHorizontally="true"
+            android:hint="@string/dialpad_number_hint"
+            tools:ignore="LabelFor,TextFields,Deprecated" />
+
+        <ImageButton
+            android:id="@+id/dialpad_del"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@string/dialpad_del"
+            android:src="@drawable/sym_keyboard_delete" />
+
+    </LinearLayout>
+
+    <GridView
+        android:id="@+id/dialpad"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:numColumns="4" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical" >
+
+        <Button
+            android:id="@+id/dialpad_dial"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1.5"
+            android:text="@string/dialpad_dial" />
+
+        <Button
+            android:id="@+id/dialpad_memdial"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1.5"
+            android:text="@string/dialpad_memdial" />
+
+        <ToggleButton
+            android:id="@+id/dialpad_dtmf"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:textOn="@string/dialpad_dtmf"
+            android:textOff="@string/dialpad_dtmf" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/indicators_fragment.xml b/bttestapp/res/layout/indicators_fragment.xml
new file mode 100644
index 0000000..473bc81
--- /dev/null
+++ b/bttestapp/res/layout/indicators_fragment.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <ToggleButton
+            android:id="@+id/ind_conn_state"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:textOn="@string/ind_conn_state"
+            android:textOff="@string/ind_conn_state" />
+
+        <ToggleButton
+            android:id="@+id/ind_audio_state"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:textOn="@string/ind_audio_state"
+            android:textOff="@string/ind_audio_state" />
+
+        <ToggleButton
+            android:id="@+id/ind_vr_state"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:textOn="@string/ind_vr_state"
+            android:textOff="@string/ind_vr_state" />
+
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/ind_operator"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:text="@string/ind_operator_unknown"
+        android:textAppearance="?android:attr/textAppearanceLarge" />
+
+    <TextView
+        android:id="@+id/ind_subscriber"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:text="@string/ind_subscriber_unknown" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="right"
+            android:text="@string/ind_signal_level"
+            style="@style/indicator"
+            android:background="#00000000" />
+
+        <TextView
+            android:id="@+id/ind_signal_level"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="left"
+            style="@style/indicator"
+            android:background="#00000000" />
+
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="right"
+            android:text="@string/ind_battery_level"
+            style="@style/indicator"
+            android:background="#00000000" />
+
+        <TextView
+            android:id="@+id/ind_battery_level"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="left"
+            style="@style/indicator"
+            android:background="#00000000" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/ind_network_state"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/ind_network_state"
+            style="@style/indicator" />
+
+        <TextView
+            android:id="@+id/ind_roaming_state"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/ind_roaming_state"
+            style="@style/indicator" />
+
+        <TextView
+            android:id="@+id/ind_inband_state"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/ind_inband_state"
+            style="@style/indicator" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/message_row.xml b/bttestapp/res/layout/message_row.xml
new file mode 100644
index 0000000..11a051b
--- /dev/null
+++ b/bttestapp/res/layout/message_row.xml
@@ -0,0 +1,179 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingTop="2dp"
+    android:paddingBottom="2dp" >
+
+    <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="#303030"
+            style="@style/someSmallPadding" >
+
+        <TextView
+            android:id="@+id/message_row_date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingRight="20dp"
+            android:typeface="monospace"
+            android:layout_alignParentLeft="true" />
+
+        <TextView
+            android:id="@+id/message_row_type"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ems="6"
+            android:typeface="monospace"
+            android:paddingRight="20dp"
+            android:layout_toRightOf="@id/message_row_date" />
+
+        <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_toRightOf="@id/message_row_type"
+                android:orientation="horizontal" >
+
+            <TextView
+                android:id="@+id/message_row_flag_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="3dp"
+                android:typeface="monospace"
+                android:text="@string/msgflag_text" />
+
+            <TextView
+                android:id="@+id/message_row_flag_read"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="3dp"
+                android:layout_marginRight="3dp"
+                android:typeface="monospace"
+                android:text="@string/msgflag_read" />
+
+            <TextView
+                android:id="@+id/message_row_flag_sent"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="3dp"
+                android:layout_marginRight="3dp"
+                android:typeface="monospace"
+                android:text="@string/msgflag_sent" />
+
+            <TextView
+                android:id="@+id/message_row_flag_drm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="3dp"
+                android:layout_marginRight="3dp"
+                android:typeface="monospace"
+                android:text="@string/msgflag_drm" />
+
+            <TextView
+                android:id="@+id/message_row_flag_prio"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="3dp"
+                android:typeface="monospace"
+                android:text="@string/msgflag_prio" />
+
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/message_row_handle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:typeface="monospace"
+            android:layout_alignParentRight="true" />
+
+    </RelativeLayout>
+
+    <TextView
+        android:id="@+id/message_row_subject"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textStyle="bold"
+        android:background="#202020"
+        style="@style/someSmallPadding" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="#202020"
+        style="@style/someSmallPadding" >
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="horizontal" >
+
+            <TextView
+                android:id="@+id/message_row_from_lbl"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textStyle="bold"
+                android:layout_marginRight="5dp"
+                android:text="@string/map_msg_row_from" />
+
+            <TextView
+                android:id="@+id/message_row_from"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="horizontal" >
+
+            <TextView
+                android:id="@+id/message_row_to_lbl"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textStyle="bold"
+                android:layout_marginRight="5dp"
+                android:text="@string/map_msg_row_to" />
+
+            <TextView
+                android:id="@+id/message_row_to"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/messages_filter.xml b/bttestapp/res/layout/messages_filter.xml
new file mode 100644
index 0000000..ab205da
--- /dev/null
+++ b/bttestapp/res/layout/messages_filter.xml
@@ -0,0 +1,235 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical" >
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_filter_without_message_type" />
+
+            <CheckBox
+                android:id="@+id/map_filter_type_sms_gsm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_msg_type_sms_gsm" />
+
+            <CheckBox
+                android:id="@+id/map_filter_type_sms_cdma"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_msg_type_sms_cdma" />
+
+            <CheckBox
+                android:id="@+id/map_filter_type_email"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_msg_type_email" />
+
+            <CheckBox
+                android:id="@+id/map_filter_type_mms"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_msg_type_mms" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_filter_read_status" />
+
+            <RadioGroup
+                android:id="@+id/map_filter_read_status"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" >
+
+                <RadioButton
+                    android:id="@+id/map_filter_read_status_all"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/map_filter_any_status"
+                    android:checked="true" />
+
+                <RadioButton
+                    android:id="@+id/map_filter_read_status_unread"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/map_filter_read_status_unread" />
+
+                <RadioButton
+                    android:id="@+id/map_filter_read_status_read"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/map_filter_read_status_read" />
+
+            </RadioGroup>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical" >
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_filter_priority" />
+
+            <RadioGroup
+                android:id="@+id/map_filter_priority"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" >
+
+                <RadioButton
+                    android:id="@+id/map_filter_priority_all"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/map_filter_any_priority"
+                    android:checked="true" />
+
+                <RadioButton
+                    android:id="@+id/map_filter_priority_high"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/map_filter_priority_high" />
+
+                <RadioButton
+                    android:id="@+id/map_filter_status_non_high"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/map_filter_priority_non_high" />
+
+            </RadioGroup>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <TableLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:focusableInTouchMode="true"
+        android:stretchColumns="1" >
+
+        <TableRow>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_filter_period_begin" />
+
+            <EditText
+                android:id="@+id/map_filter_period_begin"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <ImageButton
+                android:id="@+id/map_pick_data_period_begin"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:src="@android:drawable/ic_menu_today" />
+
+        </TableRow>
+
+        <TableRow>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_filter_period_end" />
+
+            <EditText
+                android:id="@+id/map_filter_period_end"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <ImageButton
+                android:id="@+id/map_pick_data_period_end"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:src="@android:drawable/ic_menu_today" />
+        </TableRow>
+
+    </TableLayout>
+
+    <TableLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:stretchColumns="1" >
+
+        <TableRow>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_filter_recipient" />
+
+            <EditText
+                android:id="@+id/map_filter_recipient"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+        </TableRow>
+
+        <TableRow>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/map_filter_originator" />
+
+            <EditText
+                android:id="@+id/map_filter_originator"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </TableRow>
+    </TableLayout>
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/pbap_auth.xml b/bttestapp/res/layout/pbap_auth.xml
new file mode 100644
index 0000000..b3f7988
--- /dev/null
+++ b/bttestapp/res/layout/pbap_auth.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright (c) 2013, The Linux Foundation. All rights reserved.
+** Not a Contribution.
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent">
+
+    <LinearLayout
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="20dip"
+        android:gravity="center_horizontal"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <EditText
+        android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dip"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="20dip"
+        android:singleLine="true" />
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/bttestapp/res/layout/service_row.xml b/bttestapp/res/layout/service_row.xml
new file mode 100644
index 0000000..975b0e6
--- /dev/null
+++ b/bttestapp/res/layout/service_row.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="4dp" >
+
+    <LinearLayout
+        android:id="@+id/service_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_centerVertical="true"
+        android:layout_alignParentLeft="true" >
+
+        <TextView
+            android:id="@+id/service_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        <TextView
+            android:id="@+id/service_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall" />
+
+    </LinearLayout>
+
+    <Switch
+        android:id="@+id/service_switch"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_alignParentRight="true"
+        android:focusable="false" />
+
+    <Switch
+        android:id="@+id/notification_switch"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toLeftOf="@id/service_switch"
+        android:focusable="false"
+        android:visibility="gone" />
+
+</RelativeLayout>
diff --git a/bttestapp/res/layout/string_picker_dialog.xml b/bttestapp/res/layout/string_picker_dialog.xml
new file mode 100644
index 0000000..2ca09d5
--- /dev/null
+++ b/bttestapp/res/layout/string_picker_dialog.xml
@@ -0,0 +1,41 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+    <EditText
+        android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inputType="text"/>
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/vcard_row.xml b/bttestapp/res/layout/vcard_row.xml
new file mode 100644
index 0000000..5458a9b
--- /dev/null
+++ b/bttestapp/res/layout/vcard_row.xml
@@ -0,0 +1,56 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:padding="4dip" >
+
+    <ImageView
+        android:id="@+id/vcard_photo"
+        android:contentDescription="@string/blank"
+        android:layout_width="50dip"
+        android:layout_height="50dip"
+        android:layout_gravity="center_vertical"
+        android:scaleType="centerCrop"
+        android:src="@drawable/ic_contact_picture" />
+
+    <TextView
+        android:id="@+id/vcard_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginLeft="15dp"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:text="@string/blank"
+        android:textAppearance="?android:attr/textAppearanceLarge" />
+
+</LinearLayout>
diff --git a/bttestapp/res/layout/vcard_view.xml b/bttestapp/res/layout/vcard_view.xml
new file mode 100644
index 0000000..4a9a4e6
--- /dev/null
+++ b/bttestapp/res/layout/vcard_view.xml
@@ -0,0 +1,90 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/scrollView1"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:padding="15dp" >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="15dp"
+            android:orientation="horizontal" >
+
+            <ImageView
+                android:id="@+id/contact_photo"
+                android:contentDescription="@string/blank"
+                android:layout_width="50dip"
+                android:layout_height="50dip"
+                android:layout_gravity="center_vertical"
+                android:scaleType="centerCrop"
+                android:src="@drawable/ic_contact_picture" />
+
+            <TextView
+                android:id="@+id/contact_display_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:paddingLeft="15dp"
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        </LinearLayout>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/contact_phonelist_hdr"
+            style="?android:attr/listSeparatorTextViewStyle" />
+        <LinearLayout
+            android:id="@+id/contact_phonelist"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/contact_emaillist_hdr"
+            style="?android:attr/listSeparatorTextViewStyle" />
+        <LinearLayout
+            android:id="@+id/contact_emaillist"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical" />
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/bttestapp/res/menu/menu_filter.xml b/bttestapp/res/menu/menu_filter.xml
new file mode 100644
index 0000000..1ffb500
--- /dev/null
+++ b/bttestapp/res/menu/menu_filter.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@+id/menu_save"
+          android:icon="@drawable/ic_menu_save"
+          android:title="@string/menu_save"
+          android:showAsAction="withText|ifRoom" />
+
+</menu>
diff --git a/bttestapp/res/menu/menu_hfp_test.xml b/bttestapp/res/menu/menu_hfp_test.xml
new file mode 100644
index 0000000..bea483f
--- /dev/null
+++ b/bttestapp/res/menu/menu_hfp_test.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@+id/menu_call_history"
+          android:title="@string/menu_call_history"
+          android:showAsAction="ifRoom|withText" />
+
+    <item android:id="@+id/menu_request_phone_num"
+          android:title="@string/menu_request_phone_num"
+          android:showAsAction="ifRoom|withText" />
+
+</menu>
diff --git a/bttestapp/res/menu/menu_map_test.xml b/bttestapp/res/menu/menu_map_test.xml
new file mode 100644
index 0000000..e9ac920
--- /dev/null
+++ b/bttestapp/res/menu/menu_map_test.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- Three-dots submenu items -->
+    <item android:id="@+id/menu_map_update_inbox"
+          android:title="@string/map_update_inbox"
+          android:showAsAction="never"
+          android:visible="false" />
+
+    <item android:id="@+id/menu_map_connect"
+          android:title="@string/menu_connect"
+          android:showAsAction="never"
+          android:visible="false" />
+
+    <item android:id="@+id/menu_map_disconnect"
+          android:title="@string/menu_disconnect"
+          android:showAsAction="never"
+          android:visible="false" />
+
+    <item
+        android:id="@+id/menu_map_goto_inbox"
+        android:title="@string/map_goto_inbox"
+        android:showAsAction="never" />
+
+    <item
+        android:id="@+id/menu_map_goto_outbox"
+        android:title="@string/map_goto_outbox"
+        android:showAsAction="never" />
+
+    <item
+        android:id="@+id/menu_map_goto_draft"
+        android:title="@string/map_goto_draft"
+        android:showAsAction="never" />
+</menu>
diff --git a/bttestapp/res/menu/menu_pbap_test.xml b/bttestapp/res/menu/menu_pbap_test.xml
new file mode 100644
index 0000000..c117c17
--- /dev/null
+++ b/bttestapp/res/menu/menu_pbap_test.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_pbap_connect"
+          android:icon="@drawable/ic_bt_disconnected"
+          android:title="@string/menu_connect"
+          android:showAsAction="withText|always"
+          android:visible="false" />
+
+    <item android:id="@+id/menu_pbap_disconnect"
+          android:icon="@drawable/ic_bt_connected"
+          android:title="@string/menu_disconnect"
+          android:showAsAction="withText|always"
+          android:visible="false" />
+</menu>
diff --git a/bttestapp/res/values-v11/styles.xml b/bttestapp/res/values-v11/styles.xml
new file mode 100644
index 0000000..e8dcb93
--- /dev/null
+++ b/bttestapp/res/values-v11/styles.xml
@@ -0,0 +1,40 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<resources>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/bttestapp/res/values-v14/styles.xml b/bttestapp/res/values-v14/styles.xml
new file mode 100644
index 0000000..f316629
--- /dev/null
+++ b/bttestapp/res/values-v14/styles.xml
@@ -0,0 +1,41 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<resources>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="@android:style/Theme.DeviceDefault">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/bttestapp/res/values/colors.xml b/bttestapp/res/values/colors.xml
new file mode 100644
index 0000000..4011f13
--- /dev/null
+++ b/bttestapp/res/values/colors.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<resources>
+    <!--
+        <color name="color_white">#ffffff</color>
+    -->
+
+    <color name="ind_on">#008000</color>
+    <color name="ind_off">#404040</color>
+    <color name="ind_text_unknown">#404040</color>
+    <color name="ind_bar_full">#008000</color>
+    <color name="ind_bar_medium">#804000</color>
+    <color name="ind_bar_low">#800000</color>
+    <color name="ind_bar_off">#404040</color>
+
+    <color name="call_unknown">#202020</color>
+    <color name="call_new">#000080</color>
+    <color name="call_held">#804000</color>
+    <color name="call_active">#008000</color>
+    <color name="call_selected">#50800000</color>
+
+</resources>
diff --git a/bttestapp/res/values/dimens.xml b/bttestapp/res/values/dimens.xml
new file mode 100644
index 0000000..5a78874
--- /dev/null
+++ b/bttestapp/res/values/dimens.xml
@@ -0,0 +1,36 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<resources>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+</resources>
diff --git a/bttestapp/res/values/strings.xml b/bttestapp/res/values/strings.xml
new file mode 100644
index 0000000..cf8bcc0
--- /dev/null
+++ b/bttestapp/res/values/strings.xml
@@ -0,0 +1,330 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<resources>
+
+    <item type="id" name="dialpad_button" />
+    <item type="id" name="dialpad_button_pos" />
+
+<!--
+<string name=""></string>
+-->
+    <!-- General -->
+    <string name="app_name">BTTestApp</string>
+    <string name="blank"></string>
+    <string name="cancel">Cancel</string>
+
+    <string name="ppl_add">+</string>
+    <string name="ppl_del">-</string>
+
+    <!-- Main -->
+    <string name="services_header">Available services</string>
+    <string name="select_device">Select</string>
+    <string name="discover_services">Discover</string>
+    <string name="clear">Clear</string>
+    <string name="list_empty">No records found!</string>
+
+    <!-- Dialogs -->
+    <string name="dialog_title_create_msg_filter">Create message filter:</string>
+    <string name="dialog_title_pick_date_time">Pick date and time:</string>
+
+    <!-- Titles -->
+    <string name="title_hfp_test">HFP Test</string>
+    <string name="title_pbap_test">PBAP Test</string>
+    <string name="title_map_test">MAP Test</string>
+    <string name="title_vcard_filter">Filter Attributes</string>
+    <string name="title_message_filter">Select Message Parameters</string>
+
+    <!-- PBAP -->
+    <string name="phonebook_prompt">Choose a phonebook:</string>
+    <string name="vcard_format_prompt">Choose a vCard format:</string>
+    <string name="max_list_count_prompt">MaxListCount:</string>
+    <string name="vcard_header_prompt">vCard header:</string>
+    <string name="offset_prompt">Offset value:</string>
+    <string name="search">Search</string>
+    <string name="getsize">Get size</string>
+    <string name="abort">Abort</string>
+    <string name="pbap_session_key_dialog_header">OBEX session key required</string>
+    <string name="pbap_session_key_dialog_title">Type session key for %1$s</string>
+    <string name="pbap_authentication_timeout_message">There was time out to input session key with %1$s</string>
+    <string name="auth_notif_ticker">OBEX authentication request</string>
+    <string name="auth_notif_title">Session key</string>
+    <string name="auth_notif_message">Type session key for %1$s</string>
+    <string name="get_vcard">Get vCard</string>
+
+    <string name="search_attribute_prompt">Search Attribute:</string>
+    <string name="search_value_prompt">Search Value:</string>
+    <string name="search_attribute_name">Name</string>
+    <string name="search_attribute_number">Number</string>
+
+    <string name="order_attribute_unordered">Default</string>
+    <string name="order_attribute_alphabetical">Alphabetical</string>
+    <string name="order_attribute_indexed">Indexed</string>
+    <string name="order_attribute_phonetic">Phonetic</string>
+    <string name="search_attribute_sound">Sound</string>
+
+    <string name="filter_button">Select Filter Attributes</string>
+    <string name="filter_field_version">vCard Version</string>
+    <string name="filter_field_fn">Formatted Name</string>
+    <string name="filter_field_n">Structured Presentation of Name</string>
+    <string name="filter_field_photo">Associated Image or Photo</string>
+    <string name="filter_field_bday">Birthday</string>
+    <string name="filter_field_adr">Delivery Address</string>
+    <string name="filter_field_label">Delivery</string>
+    <string name="filter_field_tel">Telephone Number</string>
+    <string name="filter_field_email">Electronic Mail Address</string>
+    <string name="filter_field_mailer">Electronic Mail</string>
+    <string name="filter_field_tz">Time Zone</string>
+    <string name="filter_field_geo">Geographic Position</string>
+    <string name="filter_field_title">Job</string>
+    <string name="filter_field_role">Role within the Organization</string>
+    <string name="filter_field_logo">Organization Logo</string>
+    <string name="filter_field_agent">vCard of Person Representing</string>
+    <string name="filter_field_org">Name of Organization</string>
+    <string name="filter_field_note">Comments</string>
+    <string name="filter_field_rev">Revision</string>
+    <string name="filter_field_sound">Pronunciation of Name</string>
+    <string name="filter_field_url">Uniform Resource Locator</string>
+    <string name="filter_field_uid">Unique ID</string>
+    <string name="filter_field_key">Public Encryption Key</string>
+    <string name="filter_field_nickname">Nickname</string>
+    <string name="filter_field_categories">Categories</string>
+    <string name="filter_field_proid">Product ID</string>
+    <string name="filter_field_class">Class information</string>
+    <string name="filter_field_sort_string">String used for sorting operations</string>
+    <string name="filter_field_x_irmc_call_datetime">Time stamp</string>
+    <string name="filter_field_filter">Indicates the usage of a filter</string>
+
+    <string name="vcard21">vCard 2.1</string>
+    <string name="vcard30">vCard 3.0</string>
+
+    <string name="set_phonebook">SetPhonebook</string>
+
+    <!-- HLS -->
+
+    <!-- MAP -->
+    <string name="msgflag_text">TEXT</string>
+    <string name="msgflag_read">READ</string>
+    <string name="msgflag_sent">SENT</string>
+    <string name="msgflag_drm">DRM</string>
+    <string name="msgflag_prio">PRIO</string>
+    <string name="map_goto_inbox">go to inbox</string>
+    <string name="map_goto_outbox">go to outbox</string>
+    <string name="map_goto_draft">go to draft</string>
+    <string name="map_msg_copy_to_editor_lbl">Copy &gt;</string>
+    <string name="map_msg_push_lbl">Push message &gt;</string>
+    <string name="map_msg_push_transparent_lbl">transparent</string>
+    <string name="map_msg_push_retry_lbl">retry</string>
+    <string name="charset_native_lbl">native</string>
+    <string name="charset_utf8_lbl">UTF-8</string>
+    <string name="map_msg_get_lbl">Get message &gt;</string>
+    <string name="map_msg_get_attachment_lbl">w/attach</string>
+    <string name="map_folder_root">/</string>
+    <string name="map_folder_up">..</string>
+    <string name="map_folder_enter">-></string>
+    <string name="map_folder_listing">list</string>
+    <string name="map_folder_listing_size">size</string>
+    <string name="map_message_listing">Get Message Listing</string>
+    <string name="map_message_listing_size">Get Message Listing Size</string>
+    <string name="map_update_inbox">Update Inbox</string>
+    <string name="map_delete_message">Delete</string>
+    <string name="map_max_list_count_prompt">Max list count:</string>
+    <string name="map_list_start_offset_prompt">Start offset:</string>
+    <string name="map_subject_length_prompt">Subject length:</string>
+    <string name="map_parameter_subject">Subject</string>
+    <string name="map_parameter_datetime">Datetime</string>
+    <string name="map_parameter_sender_name">Sender Name</string>
+    <string name="map_parameter_sender_addressing">Sender Addressing</string>
+    <string name="map_parameter_recipient_name">Recipient Name</string>
+    <string name="map_parameter_recipient_addressing">Recipient Addressing</string>
+    <string name="map_parameter_type">Type</string>
+    <string name="map_parameter_size">Size</string>
+    <string name="map_parameter_reception_status">Reception Status</string>
+    <string name="map_parameter_text">Text</string>
+    <string name="map_parameter_attachment_size">Attachment Size</string>
+    <string name="map_parameter_priority">Priority</string>
+    <string name="map_parameter_read">Read</string>
+    <string name="map_parameter_sent">Sent</string>
+    <string name="map_parameter_protected">Protected</string>
+    <string name="map_parameter_replyto_addressing">Replayto Addressing</string>
+    <string name="map_message_parameters">Parameters</string>
+    <string name="map_message_filter">Filter</string>
+    <string name="map_msg_row_from">&lt;</string>
+    <string name="map_msg_row_to">&gt;</string>
+    <string name="map_msg_type_sms_gsm">SMS GSM</string>
+    <string name="map_msg_type_sms_cdma">SMS CDMA</string>
+    <string name="map_msg_type_email">EMAIL</string>
+    <string name="map_msg_type_mms">MMS</string>
+    <string name="map_filter_any_status">Any status</string>
+    <string name="map_filter_any_priority">Any priority</string>
+    <string name="map_filter_without_message_type">Filter out message type:</string>
+    <string name="map_filter_read_status">Read status:</string>
+    <string name="map_filter_read_status_unread">Unread</string>
+    <string name="map_filter_read_status_read">Read</string>
+    <string name="map_filter_priority">Priority:</string>
+    <string name="map_filter_priority_high">High</string>
+    <string name="map_filter_priority_non_high">Non-high</string>
+    <string name="map_filter_period_begin">Period begin:</string>
+    <string name="map_filter_period_end">Period end:</string>
+    <string name="map_filter_recipient">Recipient:</string>
+    <string name="map_filter_originator">Originator:</string>
+    <string name="map_messages_list_empty">No messages found!</string>
+    <string name="map_set_read">Set READ</string>
+    <string name="map_set_unread">Set UNREAD</string>
+    <string name="map_recipients_prompt">Recipients:</string>
+    <string name="map_originators_prompt">Originators:</string>
+    <string name="map_bmsg_status_prompt">Status:</string>
+    <string name="map_bmsg_type_prompt">Type:</string>
+    <string name="map_bmsg_folder_prompt">Folder:</string>
+    <string name="map_bbody_encoding_prompt">Encoding:</string>
+    <string name="map_bbody_charset_prompt">Charset:</string>
+    <string name="map_bbody_language_prompt">Language:</string>
+    <string name="map_report_notif_ticker">New MAP notification report received!</string>
+    <string name="map_report_notif_title_delivery_success">Delivery success</string>
+    <string name="map_report_notif_title_sending_success">Sending success</string>
+    <string name="map_report_notif_title_delivery_failure">Delivery failure</string>
+    <string name="map_report_notif_title_sending_failure">Sending failure</string>
+    <string name="map_report_notif_title_memory_full">Memory full</string>
+    <string name="map_report_notif_title_memory_available">Memory available</string>
+    <string name="map_report_notif_title_message_deleted">Message deleted</string>
+    <string name="map_report_notif_received">Received new %1$s message</string>
+    <string name="map_report_notif_handle">Handle: %1$s</string>
+    <string name="map_report_notif_shifted">Message %1$s shifted</string>
+    <string name="map_report_notif_fromto">From %1$s to %2$s</string>
+
+    <string-array name="map_message_type_array">
+        <item>SMS_GSM</item>
+        <item>SMS_CDMA</item>
+        <item>EMAIL</item>
+        <item>MMS</item>
+    </string-array>
+
+    <string-array name="map_message_encoding_array">
+        <item></item>
+        <item>8BIT</item>
+        <item>G-7BIT</item>
+        <item>G-7BITEXT</item>
+        <item>G-UCS2</item>
+        <item>G-8BIT</item>
+        <item>C-8BIT</item>
+        <item>C-EPM</item>
+        <item>C-7ASCII</item>
+        <item>C-IA5</item>
+        <item>C-UNICODE</item>
+        <item>C-SJIS</item>
+        <item>C-KOREAN</item>
+        <item>C-LATINHEB</item>
+        <item>C-LATIN</item>
+    </string-array>
+
+    <string-array name="phonebook_arrays">
+        <item>telecom/pb.vcf</item>
+        <item>telecom/ich.vcf</item>
+        <item>telecom/och.vcf</item>
+        <item>telecom/mch.vcf</item>
+        <item>telecom/cch.vcf</item>
+        <item>SIM1/telecom/pb.vcf</item>
+        <item>SIM1/telecom/ich.vcf</item>
+        <item>SIM1/telecom/och.vcf</item>
+        <item>SIM1/telecom/mch.vcf</item>
+        <item>SIM1/telecom/cch.vcf</item>
+    </string-array>
+
+    <!-- Contact Details -->
+    <string name="contact_phonelist_hdr">Phone list</string>
+    <string name="contact_emaillist_hdr">E-mail list</string>
+
+    <!-- Menu -->
+    <string name="menu_save">Save</string>
+    <string name="menu_connect">Connect</string>
+    <string name="menu_disconnect">Disconnect</string>
+
+    <!-- Other -->
+    <string name="vcard">vCard</string>
+    <string name="download">Download</string>
+    <string name="browse">Browse</string>
+
+    <!-- Toast messages -->
+    <string name="msg_connection_success">Device has been connected.</string>
+    <string name="msg_disconnection_success">Device has been disconnected.</string>
+
+
+    <!-- HFP test activity -->
+    <string name="hfptest_call_state_unknown_text">unknown</string>
+    <string name="menu_request_phone_num">Request phone number</string>
+    <string name="menu_call_history">Call history</string>
+    <string name="call_history_title">Select number:</string>
+    <string name="dialpad_number_hint">Click &quot;<sup><small>Re</small></sup>Dial&quot; to redial</string>
+    <string name="dialpad_del">Delete</string>
+    <string name="dialpad_dial"><sup><small>Re</small></sup>Dial</string>
+    <string name="dialpad_memdial">Mem dial</string>
+    <string name="dialpad_dtmf">DTMF</string>
+    <string name="ind_conn_state">CONN</string>
+    <string name="ind_audio_state">AUDIO</string>
+    <string name="ind_vr_state">VR</string>
+    <string name="ind_inband_state">IN-BAND</string>
+    <string name="ind_network_state">NETWORK</string>
+    <string name="ind_roaming_state">ROAMING</string>
+    <string name="ind_signal_level">signal</string>
+    <string name="ind_battery_level">battery</string>
+    <string name="ind_operator_unknown">no operator info</string>
+    <string name="ind_subscriber_unknown">no subscriber info</string>
+    <string name="ind_bar">&#x258c;&#x258c;&#x258c;&#x258c;&#x258c;</string>
+    <string name="call_state_active">ACTIVE</string>
+    <string name="call_state_held">HELD</string>
+    <string name="call_state_dialing">DIALING</string>
+    <string name="call_state_alerting">ALERTING</string>
+    <string name="call_state_incoming">INCOMING</string>
+    <string name="call_state_waiting">WAITING</string>
+    <string name="call_state_held_by_rnh">R&amp;H</string>
+    <string name="call_multiparty">MULTIPARTY</string>
+    <string name="call_action_accept">Accept</string>
+    <string name="call_action_accept_held">Accept held</string>
+    <string name="call_action_merge">Merge calls</string>
+    <string name="call_action_hold_and_accept">Hold and accept</string>
+    <string name="call_action_swap">Swap calls</string>
+    <string name="call_action_release_and_accept">Release and accept</string>
+    <string name="call_action_reject">Reject</string>
+    <string name="call_action_reject_held">Reject held</string>
+    <string name="call_action_terminate">Terminate</string>
+    <string name="call_action_hold">Hold</string>
+    <string name="call_action_respond_and_hold">Respond and hold</string>
+    <string name="call_action_private_mode">Private mode</string>
+    <string name="call_action_explicit_transfer">Explicit transfer</string>
+
+        <!-- HFP result codes messages -->
+    <string name="hfptest_result_ok_text">OK</string>
+    <string name="hfptest_result_error_text">Error</string>
+    <string name="hfptest_result_no_carrier_text">Error: no carrier</string>
+    <string name="hfptest_result_busy_text">Error: busy</string>
+    <string name="hfptest_result_no_answer_text">Error: no answar</string>
+    <string name="hfptest_result_delayed_text">Error: delayed</string>
+    <string name="hfptest_result_blacklisted_text">Error: blacklisted</string>
+    <string name="hfptest_result_cme_text">Error: CME</string>
+</resources>
diff --git a/bttestapp/res/values/styles.xml b/bttestapp/res/values/styles.xml
new file mode 100644
index 0000000..00ea0d5
--- /dev/null
+++ b/bttestapp/res/values/styles.xml
@@ -0,0 +1,78 @@
+<!--
+/*
+ * 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.
+ */
+-->
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+    <style name="TextViewAsLabel">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:background">#525252</item>
+        <item name="android:textColor">#c0c0c0</item>
+        <item name="android:layout_marginTop">3dp</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="somePadding">
+        <item name="android:paddingLeft">20dp</item>
+        <item name="android:paddingRight">20dp</item>
+    </style>
+
+    <style name="someSmallPadding">
+        <item name="android:paddingLeft">10dp</item>
+        <item name="android:paddingRight">10dp</item>
+        <item name="android:paddingTop">2dp</item>
+        <item name="android:paddingBottom">2dp</item>
+    </style>
+
+    <style name="indicator">
+        <item name="android:layout_marginTop">5dp</item>
+        <item name="android:layout_marginBottom">5dp</item>
+        <item name="android:layout_marginLeft">5dp</item>
+        <item name="android:layout_marginRight">5dp</item>
+        <item name="android:gravity">center</item>
+    </style>
+
+</resources>
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/ActivityHelper.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/ActivityHelper.java
new file mode 100644
index 0000000..f91ae3d
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/ActivityHelper.java
@@ -0,0 +1,53 @@
+/*
+ * 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.bttestapp;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.pm.ActivityInfo;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+public class ActivityHelper {
+    public static void initialize(Activity activity, int layout) {
+        // Do all sorts of common task for your activities here including:
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+        activity.setContentView(layout);
+        activity.getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
+        activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
+                | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
+        activity.getWindow().setSoftInputMode(
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+    }
+
+    public static void setActionBarTitle(Activity activity, int title) {
+        activity.getActionBar().setTitle(title);
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/BluetoothConnectionReceiver.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/BluetoothConnectionReceiver.java
new file mode 100644
index 0000000..0f276c8
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/BluetoothConnectionReceiver.java
@@ -0,0 +1,104 @@
+/*
+ * 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.bttestapp;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import org.codeaurora.bluetooth.bttestapp.util.Logger;
+
+import java.util.ArrayList;
+
+public class BluetoothConnectionReceiver extends BroadcastReceiver {
+
+    private final static String TAG = "BluetoothConnectionReceiver";
+
+    public static final String ACTION_NEW_BLUETOOTH_DEVICE = "com.qualcomm.bluetooth.action.NEW_BLUETOOTH_DEVICE";
+
+    public static final String EXTRA_DEVICE_ADDRESS = "com.qualcomm.bluetooth.extra.DEVICE_ADDRESS";
+
+    private static ArrayList<IBluetoothConnectionObserver> observers = new ArrayList<IBluetoothConnectionObserver>();
+
+    private static BluetoothDevice selectedDevice = null;
+
+    public static void registerObserver(IBluetoothConnectionObserver observer) {
+        observers.add(observer);
+
+        if (selectedDevice != null) {
+            observer.onDeviceChanged(selectedDevice);
+        }
+    }
+
+    public static void removeObserver(IBluetoothConnectionObserver observer) {
+        observers.remove(observer);
+    }
+
+    private void notifyObserversDeviceChanged(BluetoothDevice device) {
+        for (IBluetoothConnectionObserver observer : observers) {
+            observer.onDeviceChanged(device);
+        }
+    }
+
+    private void notifyObserversDeviceDisconected() {
+        for (IBluetoothConnectionObserver observer : observers) {
+            observer.onDeviceDisconected();
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (ACTION_NEW_BLUETOOTH_DEVICE.equals(intent.getAction())) {
+            Logger.v(TAG, "Receive new bluetooth device.");
+
+            String address = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
+
+            if (address != null) {
+                BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+                selectedDevice = adapter.getRemoteDevice(address);
+                notifyObserversDeviceChanged(selectedDevice);
+            } else {
+                Logger.e(TAG, "Received NULL address!");
+            }
+        } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(intent.getAction())) {
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            if (device.equals(selectedDevice)) {
+                Logger.v(TAG, "Received bluetooth disconected.");
+
+                notifyObserversDeviceDisconected();
+            }
+        } else {
+            Logger.w(TAG, "Unknown intent received with action: " + intent.getAction());
+        }
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/CallHistoryDialogFragment.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/CallHistoryDialogFragment.java
new file mode 100644
index 0000000..8e1cda8
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/CallHistoryDialogFragment.java
@@ -0,0 +1,90 @@
+/*
+ * 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.bttestapp;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import org.codeaurora.bluetooth.bttestapp.R;
+
+import java.util.ArrayList;
+
+class CallHistoryDialogFragment extends DialogFragment {
+
+    private final ArrayList<String> mElements;
+
+    private CallHistoryDialogListener mListener;
+
+    public interface CallHistoryDialogListener {
+        public void onCallHistoryDialogPositive(DialogFragment dialog, String number);
+    };
+
+    public CallHistoryDialogFragment(ArrayList<String> elements) {
+        super();
+
+        mElements = new ArrayList<String>(elements);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+        CharSequence[] elements = mElements.toArray(new CharSequence[mElements.size()]);
+
+        builder.setTitle(R.string.call_history_title);
+        builder.setItems(elements, new DialogInterface.OnClickListener() {
+
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                mListener.onCallHistoryDialogPositive(CallHistoryDialogFragment.this,
+                        mElements.get(which));
+            }
+        });
+        builder.setNegativeButton(android.R.string.cancel, null);
+
+        return builder.create();
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mListener = (CallHistoryDialogListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement "
+                    + CallHistoryDialogListener.class.getSimpleName());
+        }
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/CallsListFragment.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/CallsListFragment.java
new file mode 100644
index 0000000..9c87abd
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/CallsListFragment.java
@@ -0,0 +1,467 @@
+/*
+ * 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.bttestapp;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHandsfreeClient;
+import android.bluetooth.BluetoothHandsfreeClientCall;
+import android.bluetooth.BluetoothProfile;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.qualcomm.bttestapp.R;
+
+public class CallsListFragment extends Fragment implements OnClickListener, OnItemClickListener {
+
+    private final static String TAG = "CallsListFragment";
+
+    private HfpTestActivity mActivity;
+
+    private ListView mCallsList;
+
+    private Button mActionAccept;
+
+    private Button mActionHoldAndAccept;
+
+    private Button mActionReleaseAndAccept;
+
+    private Button mActionReject;
+
+    private Button mActionTerminate;
+
+    private Button mActionHold;
+
+    private Button mActionRespondAndHold;
+
+    private Button mActionPrivateMode;
+
+    private Button mActionExplicitTransfer;
+
+    private class CallsAdapter extends BaseAdapter {
+
+        private final SparseArray<BluetoothHandsfreeClientCall> mCalls;
+
+        private int mSelectedId = 0;
+
+        CallsAdapter() {
+            mCalls = new SparseArray<BluetoothHandsfreeClientCall>();
+        }
+
+        @Override
+        public int getCount() {
+            return mCalls.size();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mCalls.get(mCalls.keyAt(position));
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return mCalls.keyAt(position);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view = convertView;
+
+            BluetoothHandsfreeClientCall call = (BluetoothHandsfreeClientCall) getItem(position);
+
+            if (view == null) {
+                view = getActivity().getLayoutInflater().inflate(R.layout.call_view, parent, false);
+            }
+
+            if (mSelectedId == call.getId()) {
+                view.setBackgroundResource(R.drawable.selected_call_bg);
+            } else {
+                view.setBackgroundResource(0);
+            }
+
+            TextView callId = (TextView) view.findViewById(R.id.call_id);
+            callId.setText(Integer.toString(call.getId()));
+
+            ImageView dir = (ImageView) view.findViewById(R.id.call_direction);
+            dir.setImageResource(call.isOutgoing() ? R.drawable.ic_call_outgoing_holo_dark
+                    : R.drawable.ic_call_incoming_holo_dark);
+
+            ((TextView) view.findViewById(R.id.call_number)).setText(call.getNumber());
+
+            switch (call.getState()) {
+                case BluetoothHandsfreeClientCall.CALL_STATE_ACTIVE:
+                    callId.setBackgroundColor(getColor(R.color.call_active));
+                    break;
+
+                case BluetoothHandsfreeClientCall.CALL_STATE_HELD:
+                case BluetoothHandsfreeClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
+                    callId.setBackgroundColor(getColor(R.color.call_held));
+                    break;
+
+                case BluetoothHandsfreeClientCall.CALL_STATE_DIALING:
+                case BluetoothHandsfreeClientCall.CALL_STATE_ALERTING:
+                case BluetoothHandsfreeClientCall.CALL_STATE_INCOMING:
+                case BluetoothHandsfreeClientCall.CALL_STATE_WAITING:
+                    callId.setBackgroundColor(getColor(R.color.call_new));
+                    break;
+
+                default:
+                    callId.setBackgroundColor(getColor(R.color.call_unknown));
+                    break;
+            }
+
+            setIndicator(view, R.id.call_state_active,
+                    call.getState() == BluetoothHandsfreeClientCall.CALL_STATE_ACTIVE);
+            setIndicator(view, R.id.call_state_held,
+                    call.getState() == BluetoothHandsfreeClientCall.CALL_STATE_HELD);
+            setIndicator(view, R.id.call_state_dialing,
+                    call.getState() == BluetoothHandsfreeClientCall.CALL_STATE_DIALING);
+            setIndicator(view, R.id.call_state_alerting,
+                    call.getState() == BluetoothHandsfreeClientCall.CALL_STATE_ALERTING);
+            setIndicator(view, R.id.call_state_incoming,
+                    call.getState() == BluetoothHandsfreeClientCall.CALL_STATE_INCOMING);
+            setIndicator(view, R.id.call_state_waiting,
+                    call.getState() == BluetoothHandsfreeClientCall.CALL_STATE_WAITING);
+            setIndicator(
+                    view,
+                    R.id.call_state_held_by_rnh,
+                    call.getState() == BluetoothHandsfreeClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD);
+
+            setIndicator(view, R.id.call_multiparty, call.isMultiParty());
+
+            return view;
+        }
+
+        public void add(BluetoothHandsfreeClientCall call) {
+            mCalls.put(call.getId(), call);
+            notifyDataSetChanged();
+        }
+
+        public void remove(BluetoothHandsfreeClientCall call) {
+            if (call.getId() == mSelectedId) {
+                mSelectedId = 0;
+            }
+            mCalls.remove(call.getId());
+            notifyDataSetChanged();
+        }
+
+        public void removeAll() {
+            mCalls.clear();
+            notifyDataSetChanged();
+        }
+
+        public void toggleSelected(int id) {
+            if (id == mSelectedId) {
+                mSelectedId = 0;
+            } else {
+                mSelectedId = id;
+            }
+            notifyDataSetChanged();
+        }
+
+        public BluetoothHandsfreeClientCall getSelected() {
+            return mCalls.get(mSelectedId);
+        }
+
+        public boolean hasCallsInState(int... state) {
+            for (int idx = 0; idx < mCalls.size(); idx++) {
+                BluetoothHandsfreeClientCall call = mCalls.valueAt(idx);
+                for (int s : state) {
+                    if (call.getState() == s) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        private void setIndicator(View view, int id, boolean state) {
+            TextView txt = (TextView) view.findViewById(id);
+            txt.setVisibility(state ? View.VISIBLE : View.INVISIBLE);
+        }
+
+        private int getColor(int id) {
+            return getResources().getColor(id);
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.calls_list_fragment, null);
+
+        mCallsList = (ListView) view.findViewById(R.id.calls_list);
+        mCallsList.setAdapter(new CallsAdapter());
+        mCallsList.setOnItemClickListener(this);
+
+        mActionAccept = (Button) view.findViewById(R.id.call_action_accept);
+        mActionHoldAndAccept = (Button) view.findViewById(R.id.call_action_hold_and_accept);
+        mActionReleaseAndAccept = (Button) view.findViewById(R.id.call_action_release_and_accept);
+        mActionReject = (Button) view.findViewById(R.id.call_action_reject);
+        mActionTerminate = (Button) view.findViewById(R.id.call_action_terminate);
+        mActionHold = (Button) view.findViewById(R.id.call_action_hold);
+        mActionRespondAndHold = (Button) view.findViewById(R.id.call_action_respond_and_hold);
+        mActionPrivateMode = (Button) view.findViewById(R.id.call_action_private_mode);
+        mActionExplicitTransfer = (Button) view.findViewById(R.id.call_action_explicit_transfer);
+
+        mActionAccept.setEnabled(false);
+        mActionHoldAndAccept.setEnabled(false);
+        mActionReleaseAndAccept.setEnabled(false);
+        mActionReject.setEnabled(false);
+        mActionTerminate.setEnabled(false);
+        mActionHold.setEnabled(false);
+        mActionRespondAndHold.setEnabled(false);
+        mActionPrivateMode.setEnabled(false);
+        mActionExplicitTransfer.setEnabled(false);
+
+        mActionAccept.setOnClickListener(this);
+        mActionHoldAndAccept.setOnClickListener(this);
+        mActionReleaseAndAccept.setOnClickListener(this);
+        mActionReject.setOnClickListener(this);
+        mActionTerminate.setOnClickListener(this);
+        mActionHold.setOnClickListener(this);
+        mActionRespondAndHold.setOnClickListener(this);
+        mActionPrivateMode.setOnClickListener(this);
+        mActionExplicitTransfer.setOnClickListener(this);
+
+        return view;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mActivity = (HfpTestActivity) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(this.getClass().getSimpleName()
+                    + " can be only attached to " + HfpTestActivity.class.getSimpleName());
+        }
+    }
+
+    public void onCallChanged(BluetoothHandsfreeClientCall call) {
+        Log.v(TAG, "onCallChanged(): call=" + HfpTestActivity.callToJson(call));
+
+        CallsAdapter adapter = (CallsAdapter) mCallsList.getAdapter();
+
+        if (call.getState() == BluetoothHandsfreeClientCall.CALL_STATE_TERMINATED) {
+            adapter.remove(call);
+        } else {
+            // add will also update existing call with the same id
+            adapter.add(call);
+        }
+
+        updateActionsState();
+    }
+
+    @Override
+    public void onClick(View v) {
+        BluetoothHandsfreeClient cli = mActivity.mBluetoothHandsfreeClient;
+        BluetoothDevice device = mActivity.mDevice;
+        CallsAdapter adapter = (CallsAdapter) mCallsList.getAdapter();
+        BluetoothHandsfreeClientCall selectedCall = adapter.getSelected();
+        boolean result = true;
+
+        switch (v.getId()) {
+            case R.id.call_action_accept:
+                result = cli.acceptCall(device, BluetoothHandsfreeClient.CALL_ACCEPT_NONE);
+                break;
+
+            case R.id.call_action_hold_and_accept:
+                result = cli.acceptCall(device, BluetoothHandsfreeClient.CALL_ACCEPT_HOLD);
+                break;
+
+            case R.id.call_action_release_and_accept:
+                result = cli.acceptCall(device, BluetoothHandsfreeClient.CALL_ACCEPT_TERMINATE);
+                break;
+
+            case R.id.call_action_reject:
+                result = cli.rejectCall(device);
+                break;
+
+            case R.id.call_action_terminate:
+                result = cli.terminateCall(device, selectedCall == null ? 0 : selectedCall.getId());
+                break;
+
+            case R.id.call_action_hold:
+                result = cli.holdCall(device);
+                break;
+
+            case R.id.call_action_respond_and_hold:
+                result = cli.holdCall(device);
+                break;
+
+            case R.id.call_action_private_mode:
+                result = cli.enterPrivateMode(device, selectedCall.getId());
+                break;
+
+            case R.id.call_action_explicit_transfer:
+                result = cli.explicitCallTransfer(device);
+                break;
+        }
+
+        if (!result) {
+            Toast.makeText(getActivity(), "\"" + ((TextView) v).getText() + "\" FAILED",
+                    Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        ((CallsAdapter) parent.getAdapter()).toggleSelected((int) id);
+
+        updateActionsState();
+    }
+
+    public void onConnStateChanged(int state, int prevState) {
+        if (state == BluetoothProfile.STATE_DISCONNECTED) {
+            ((CallsAdapter) mCallsList.getAdapter()).removeAll();
+
+            updateActionsState();
+        }
+    }
+
+    private void updateActionsState() {
+        CallsAdapter adapter = (CallsAdapter) mCallsList.getAdapter();
+        BluetoothHandsfreeClientCall selectedCall = adapter.getSelected();
+
+        boolean canAccept = false;
+        boolean canHoldAndAccept = false;
+        boolean canReleaseAndAccept = false;
+        boolean canReject = false;
+        boolean canTerminate = false;
+        boolean canRespondAndHold = false;
+        boolean canHold = false;
+
+        boolean hasIncoming = adapter.hasCallsInState(BluetoothHandsfreeClientCall.CALL_STATE_INCOMING);
+        boolean hasActive = adapter.hasCallsInState(BluetoothHandsfreeClientCall.CALL_STATE_ACTIVE);
+        boolean hasWaiting = adapter.hasCallsInState(BluetoothHandsfreeClientCall.CALL_STATE_WAITING);
+        boolean hasHeld = adapter.hasCallsInState(BluetoothHandsfreeClientCall.CALL_STATE_HELD,
+                BluetoothHandsfreeClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD);
+
+        if (hasIncoming) {
+            canAccept = true;
+            canReject = mActivity.mFeatReject;
+            canRespondAndHold = true;
+
+            mActionAccept.setText(R.string.call_action_accept);
+            mActionReject.setText(R.string.call_action_reject);
+        }
+
+        if (hasActive || adapter.hasCallsInState(BluetoothHandsfreeClientCall.CALL_STATE_ALERTING,
+                                                BluetoothHandsfreeClientCall.CALL_STATE_DIALING)) {
+            canTerminate = true;
+        }
+
+        if (hasHeld) {
+            canAccept = true;
+            canReject = mActivity.mFeatReject;
+            canHoldAndAccept = mActivity.mFeatAcceptHeldOrWaiting;
+        }
+
+        if (hasWaiting) {
+            canReject = mActivity.mFeatReject;
+            mActionAccept.setText(R.string.call_action_accept);
+            mActionReject.setText(R.string.call_action_reject);
+
+            if (hasActive) {
+                canHoldAndAccept = mActivity.mFeatAcceptHeldOrWaiting;
+                canReleaseAndAccept = mActivity.mFeatReleaseAndAccept;
+
+                mActionHoldAndAccept.setText(R.string.call_action_hold_and_accept);
+            } else if (hasHeld) {
+                canAccept = true;
+                canHoldAndAccept = false;
+                canReleaseAndAccept = false;
+            }
+        }
+
+        if (adapter.hasCallsInState(BluetoothHandsfreeClientCall.CALL_STATE_ACTIVE) &&
+                !adapter.hasCallsInState(BluetoothHandsfreeClientCall.CALL_STATE_HELD,
+                        BluetoothHandsfreeClientCall.CALL_STATE_DIALING,
+                        BluetoothHandsfreeClientCall.CALL_STATE_ALERTING,
+                        BluetoothHandsfreeClientCall.CALL_STATE_INCOMING,
+                        BluetoothHandsfreeClientCall.CALL_STATE_WAITING,
+                        BluetoothHandsfreeClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD)) {
+            canHold = true;
+        }
+
+        // TODO: this should be perhaps done in some more reasonable way
+        if (hasHeld && !hasWaiting) {
+            if (hasActive) {
+                canAccept = true;
+                canReleaseAndAccept = mActivity.mFeatReleaseAndAccept;
+
+                mActionAccept.setText(R.string.call_action_merge);
+                mActionHoldAndAccept.setText(R.string.call_action_swap);
+                mActionReject.setText(R.string.call_action_reject_held);
+            } else {
+                canAccept = false;
+                mActionAccept.setText(R.string.call_action_accept);
+                mActionHoldAndAccept.setText(R.string.call_action_accept_held);
+                mActionReject.setText(R.string.call_action_reject);
+                // Actions for incoming call when there is already a held one.
+                // We cannot accept the held one now. Instead handle the incoming call first.
+                if (hasIncoming) {
+                    canAccept = true;
+                    canHoldAndAccept = false;
+                }
+            }
+        } else {
+            mActionHoldAndAccept.setText(R.string.call_action_hold_and_accept);
+        }
+
+        mActionAccept.setEnabled(canAccept);
+        mActionHoldAndAccept.setEnabled(canHoldAndAccept);
+        mActionReleaseAndAccept.setEnabled(canReleaseAndAccept);
+        mActionReject.setEnabled(canReject);
+        mActionTerminate.setEnabled(canTerminate);
+        mActionRespondAndHold.setEnabled(canRespondAndHold);
+
+        mActionHold.setEnabled(canHold);
+
+        mActionPrivateMode.setEnabled(mActivity.mFeatEnhancedCallControl && selectedCall != null && selectedCall.isMultiParty());
+
+        mActionExplicitTransfer.setEnabled(mActivity.mFeatMergeDetach && (adapter.getCount() > 1));
+    }
+}*/
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/DialpadFragment.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/DialpadFragment.java
new file mode 100644
index 0000000..3e5aa15
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/DialpadFragment.java
@@ -0,0 +1,285 @@
+/*
+ * 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.bttestapp;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.AbsListView.LayoutParams;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.EditText;
+import android.widget.GridView;
+import android.widget.ImageButton;
+import android.widget.Toast;
+import android.widget.ToggleButton;
+
+import com.qualcomm.bttestapp.R;
+
+public class DialpadFragment extends Fragment implements OnClickListener, OnLongClickListener {
+
+    private HfpTestActivity mActivity;
+
+    private GridView mButtonsGrid;
+
+    private EditText mNumberEdit;
+
+    private ToggleButton mDtmfButton;
+
+    private class ButtonAdapter extends BaseAdapter {
+
+        private final String[] mButtons = {
+                "1", "2", "3", "A",
+                "4", "5", "6", "B",
+                "7", "8", "9", "C",
+                "*", "0", "#", "D"
+        };
+
+        private final String[] mButtonsShifted = {
+                " ", " ", " ", " ",
+                " ", " ", " ", " ",
+                " ", " ", " ", " ",
+                " ", "+", " ", " "
+        };
+
+        private final Context mContext;
+
+        ButtonAdapter(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public int getCount() {
+            return mButtons.length;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return new Pair<String, String>(mButtons[position], mButtonsShifted[position]);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            Button btn;
+
+            if (convertView == null) {
+                btn = new Button(mContext);
+                btn.setLayoutParams(new LayoutParams(
+                        android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+                        android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+                btn.setOnClickListener(DialpadFragment.this);
+                btn.setOnLongClickListener(DialpadFragment.this);
+            } else {
+                btn = (Button) convertView;
+            }
+
+            @SuppressWarnings("unchecked")
+            Pair<String, String> item = (Pair<String, String>) getItem(position);
+
+            btn.setText(item.first);
+            btn.setId(R.id.dialpad_button);
+            btn.setTag(item.first);
+            btn.setTag(R.id.dialpad_button_pos, position);
+
+            return btn;
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        Context context = getActivity();
+
+        View view = inflater.inflate(R.layout.dialpad_fragment, null);
+
+        mNumberEdit = (EditText) view.findViewById(R.id.dialpad_number);
+        mNumberEdit.setOnLongClickListener(this);
+
+        mButtonsGrid = (GridView) view.findViewById(R.id.dialpad);
+        mButtonsGrid.setAdapter(new ButtonAdapter(context));
+
+        ((ImageButton) view.findViewById(R.id.dialpad_del)).setOnClickListener(this);
+        ((ImageButton) view.findViewById(R.id.dialpad_del)).setOnLongClickListener(this);
+        ((Button) view.findViewById(R.id.dialpad_dial)).setOnClickListener(this);
+        ((Button) view.findViewById(R.id.dialpad_memdial)).setOnClickListener(this);
+
+        mDtmfButton = (ToggleButton) view.findViewById(R.id.dialpad_dtmf);
+        mDtmfButton.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                getView().findViewById(R.id.dialpad_number).setEnabled(!isChecked);
+                getView().findViewById(R.id.dialpad_del).setEnabled(!isChecked);
+                getView().findViewById(R.id.dialpad_dial).setEnabled(!isChecked);
+                getView().findViewById(R.id.dialpad_memdial).setEnabled(!isChecked);
+            }
+        });
+
+        return view;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mActivity = (HfpTestActivity) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(this.getClass().getSimpleName()
+                    + " can be only attached to " + HfpTestActivity.class.getSimpleName());
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.dialpad_button:
+                int pos = (Integer) view.getTag(R.id.dialpad_button_pos);
+                onClickDialpadButton(pos, false);
+                break;
+
+            case R.id.dialpad_del:
+                onClickDelete();
+                break;
+
+            case R.id.dialpad_dial:
+                onClickDial();
+                break;
+
+            case R.id.dialpad_memdial:
+                onClickMemDial();
+                break;
+        }
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        switch (view.getId()) {
+            case R.id.dialpad_button:
+                int pos = (Integer) view.getTag(R.id.dialpad_button_pos);
+                onClickDialpadButton(pos, true);
+                return true;
+
+            case R.id.dialpad_del:
+                mNumberEdit.setText("");
+                return true;
+
+            case R.id.dialpad_number:
+                onClickDialpadNumber();
+                return true;
+        }
+
+        return false;
+    }
+
+    private void onClickDialpadNumber() {
+        ClipboardManager cm = (ClipboardManager) mNumberEdit.getContext()
+                .getSystemService(Context.CLIPBOARD_SERVICE);
+
+        String txt = mNumberEdit.getText().toString();
+
+        if (txt.isEmpty()) {
+            ClipData clipData = cm.getPrimaryClip();
+            if (clipData != null) {
+                ClipData.Item item = clipData.getItemAt(0);
+                mNumberEdit.setText(item.getText());
+            }
+        } else {
+            cm.setPrimaryClip(ClipData.newPlainText(txt, txt));
+
+            Toast.makeText(mNumberEdit.getContext(), "copied: " +
+                    txt, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private void onClickDialpadButton(int position, boolean shift) {
+        @SuppressWarnings("unchecked")
+        Pair<String, String> bval = (Pair<String, String>) mButtonsGrid.getItemAtPosition(position);
+
+        if (mDtmfButton.isChecked()) {
+            mActivity.mBluetoothHandsfreeClient.sendDTMF(mActivity.mDevice,
+                    bval.first.getBytes()[0]);
+        } else {
+            if (shift) {
+                mNumberEdit.append(bval.second.trim());
+            } else {
+                mNumberEdit.append(bval.first.trim());
+            }
+        }
+    }
+
+    private void onClickDelete() {
+        String txt = mNumberEdit.getText().toString();
+
+        if (txt.length() > 0) {
+            txt = txt.substring(0, txt.length() - 1);
+            mNumberEdit.setText(txt);
+        }
+    }
+
+    private void onClickDial() {
+        String number = mNumberEdit.getText().toString().trim();
+
+        if (number.isEmpty()) {
+            mActivity.mBluetoothHandsfreeClient.redial(mActivity.mDevice);
+        } else {
+            mActivity.mBluetoothHandsfreeClient.dial(mActivity.mDevice, mNumberEdit.getText()
+                    .toString());
+        }
+    }
+
+    private void onClickMemDial() {
+        try {
+            mActivity.mBluetoothHandsfreeClient.dialMemory(mActivity.mDevice,
+                    Integer.valueOf(mNumberEdit.getText().toString()));
+        } catch (NumberFormatException e) {
+            // just ignore
+        }
+    }
+
+    public void setNumber(String number) {
+        mNumberEdit.setText(number);
+    }
+}*/
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/FilterActivity.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/FilterActivity.java
new file mode 100644
index 0000000..4c4eb09
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/FilterActivity.java
@@ -0,0 +1,145 @@
+/*
+ * 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.bttestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.CheckBox;
+
+import org.codeaurora.bluetooth.bttestapp.R;
+import org.codeaurora.bluetooth.bttestapp.util.Logger;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public abstract class FilterActivity extends Activity {
+
+    private final String TAG = "FilterActivity";
+
+    protected Map<Integer, Integer> mParameters = new HashMap<Integer, Integer>();
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        long tmp = getIntent().getLongExtra("filter", 0);
+
+        fillParameters();
+
+        for (int i = 0; i < 64; i++) {
+            if ((tmp & 0x01) == 0x01) {
+                Integer id = mParameters.get(i);
+
+                if (id != null) {
+                    check(id);
+                }
+            }
+
+            tmp = (tmp >> 1);
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.menu_filter, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_save:
+                Intent returnIntent = new Intent();
+                returnIntent.putExtra("result", getSelectedValue());
+                setResult(RESULT_OK, returnIntent);
+                finish();
+                break;
+            default:
+                Logger.w(TAG, "Unknown item selected.");
+                break;
+        }
+        return true;
+    }
+
+    protected void check(int id) {
+        setChecked(id, true);
+    }
+
+    protected Boolean isChecked(int id) {
+        CheckBox checkbox = (CheckBox) findViewById(id);
+        if (checkbox != null) {
+            return checkbox.isChecked();
+        }
+
+        Logger.w(TAG, "Check box " + id + " not found.");
+        return false;
+    }
+
+    protected void setChecked(int id, Boolean check) {
+        CheckBox checkbox = (CheckBox) findViewById(id);
+        if (checkbox != null) {
+            checkbox.setChecked(check);
+        } else {
+            Logger.w(TAG, "Check box " + id + " not found.");
+        }
+    }
+
+    protected long getSelectedValue() {
+        long result = 0;
+
+        Iterator<Entry<Integer, Integer>> it = mParameters.entrySet().iterator();
+
+        while (it.hasNext()) {
+            Entry<Integer, Integer> pair = it.next();
+
+            Integer key = pair.getKey();
+            Integer id = pair.getValue();
+
+            if (isChecked(id)) {
+                result |= ((long) 1 << key);
+            }
+        }
+
+        return result;
+    }
+
+    protected void addParameter(Integer byteNumber, Integer id) {
+        mParameters.put(byteNumber, id);
+    }
+
+    abstract protected void fillParameters();
+
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/GetTextDialogFragment.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/GetTextDialogFragment.java
new file mode 100644
index 0000000..475ef02
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/GetTextDialogFragment.java
@@ -0,0 +1,83 @@
+/*
+ * 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.bttestapp;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.widget.EditText;
+
+import org.codeaurora.bluetooth.bttestapp.R;
+
+class GetTextDialogFragment extends DialogFragment {
+
+    private GetTextDialogListener mListener;
+
+    public interface GetTextDialogListener {
+        public void onGetTextDialogPositive(DialogFragment dialog, String text);
+    };
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+        LayoutInflater inflater = getActivity().getLayoutInflater();
+
+        builder.setView(inflater.inflate(R.layout.string_picker_dialog, null))
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        EditText edt = (EditText) GetTextDialogFragment.this.getDialog()
+                                .findViewById(R.id.text);
+                        mListener.onGetTextDialogPositive(GetTextDialogFragment.this, edt.getText()
+                                .toString());
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, null);
+
+        return builder.create();
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mListener = (GetTextDialogListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement GetTextDialogListener");
+        }
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/HfpTestActivity.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/HfpTestActivity.java
new file mode 100644
index 0000000..d62d5c4
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/HfpTestActivity.java
@@ -0,0 +1,516 @@
+/*
+ * 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.bttestapp;
+
+import android.app.ActionBar;
+import android.app.DialogFragment;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHandsfreeClient;
+import android.bluetooth.BluetoothHandsfreeClientCall;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import com.qualcomm.bttestapp.CallHistoryDialogFragment.CallHistoryDialogListener;
+import com.qualcomm.bttestapp.R;
+import com.qualcomm.bttestapp.util.Logger;
+import com.qualcomm.bttestapp.util.MonkeyEvent;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+public class HfpTestActivity extends MonkeyActivity implements IBluetoothConnectionObserver,
+        CallHistoryDialogListener {
+
+    private final String TAG = "HfpTestActivity";
+
+    private DialpadFragment mDialpadFragment = null;
+
+    private IndicatorsFragment mIndicatorsFragment = null;
+
+    private CallsListFragment mCallsListFragment = null;
+
+    private final ArrayList<String> mCallHistory = new ArrayList<String>();
+
+    private ActionBar mActionBar = null;
+
+   // /* this should be visible for fragments ///
+    BluetoothHandsfreeClient mBluetoothHandsfreeClient;
+    ProfileService mProfileService = null;
+    BluetoothDevice mDevice;
+
+    ///* this should be done in a more elegant way //
+    public boolean mFeatVtag;
+    public boolean mFeatVoiceRecognition;
+    public boolean mFeatThreeWayCalling;
+    public boolean mFeatEnhancedCallControl;
+
+    public boolean mFeatReject;
+    public boolean mFeatMerge;
+    public boolean mFeatMergeDetach;
+    public boolean mFeatReleaseAndAccept;
+    public boolean mFeatAcceptHeldOrWaiting;
+    public boolean mFeatReleaseHeldOrWaiting;
+
+    private final BroadcastReceiver mHfpClientReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            BluetoothDevice device = (BluetoothDevice)
+                    intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            if (action.equals(BluetoothHandsfreeClient.ACTION_CONNECTION_STATE_CHANGED)) {
+
+                int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
+                onReceiveActionConnectionStateChanged(device, prevState, state, intent.getExtras());
+                mCallsListFragment.onConnStateChanged(state, prevState);
+                mIndicatorsFragment.onConnStateChanged(state, prevState);
+
+                // Send MonkeyEvent
+                new MonkeyEvent("hfp-connection-state-changed", true)
+                        .addReplyParam("state", state)
+                        .addReplyParam("prevState", prevState)
+                        .send();
+            } else if (action.equals(BluetoothHandsfreeClient.ACTION_AG_EVENT)) {
+
+                mIndicatorsFragment.onAgEvent(intent.getExtras());
+
+            } else if (action.equals(BluetoothHandsfreeClient.ACTION_CALL_CHANGED)) {
+
+                BluetoothHandsfreeClientCall call = (BluetoothHandsfreeClientCall) intent.
+                        getParcelableExtra(BluetoothHandsfreeClient.EXTRA_CALL);
+                onCallChanged(call);
+                mCallsListFragment.onCallChanged(call);
+
+                // Send MonkeyEvent
+                new MonkeyEvent("hfp-call-changed", true)
+                        .addExtReply(callToJson(call))
+                        .send();
+            } else if (action.equals(BluetoothHandsfreeClient.ACTION_AUDIO_STATE_CHANGED)) {
+
+                int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
+                onReceiveAudioStateChange(device, prevState, state);
+                mIndicatorsFragment.onAudioStateChanged(state, prevState);
+
+                // Send MonkeyEvent
+                new MonkeyEvent("hfp-audio-state-changed", true)
+                        .addReplyParam("state", state)
+                        .addReplyParam("prevState", prevState)
+                        .send();
+            } else if(action.equals(BluetoothHandsfreeClient.ACTION_RESULT)) {
+                int result = intent.getIntExtra(
+                        BluetoothHandsfreeClient.EXTRA_RESULT_CODE, -1);
+                int cme = intent.getIntExtra(
+                        BluetoothHandsfreeClient.EXTRA_CME_CODE, -1);
+
+                onReceiveResult(result, cme);
+
+                // Send MonkeyEvent
+                new MonkeyEvent("hfp-receive-result", true)
+                        .addReplyParam("result", result)
+                        .addReplyParam("cme", cme)
+                        .send();
+            } else if (action.equals(BluetoothHandsfreeClient.ACTION_LAST_VTAG)) {
+
+                String number = intent.getStringExtra(BluetoothHandsfreeClient.EXTRA_NUMBER);
+                Toast.makeText(HfpTestActivity.this, "Phone number received: " + number,
+                        Toast.LENGTH_LONG).show();
+                // Send MonkeyEvent
+                new MonkeyEvent("hfp-receive-phonenumber", true)
+                        .addReplyParam("number", number)
+                        .send();
+            }
+        }
+
+        private void onCallChanged(BluetoothHandsfreeClientCall call) {
+            int state = call.getState();
+            String number = call.getNumber().trim();
+
+            new MonkeyEvent("hfp-call-changed", true).addExtReply(callToJson(call)).send();
+
+            if (number.isEmpty()) {
+                return;
+            }
+
+            /// we add any "new" call to list and put in at the beginning
+            if (state == BluetoothHandsfreeClientCall.CALL_STATE_ALERTING ||
+                    state == BluetoothHandsfreeClientCall.CALL_STATE_DIALING ||
+                    state == BluetoothHandsfreeClientCall.CALL_STATE_INCOMING ||
+                    state == BluetoothHandsfreeClientCall.CALL_STATE_WAITING) {
+                mCallHistory.remove(number);
+                mCallHistory.add(0, number);
+
+                if (mCallHistory.size() > 20) {
+                    mCallHistory.remove(20);
+                }
+            }
+        }
+
+        private void onReceiveResult(int result, int cme) {
+            Logger.v(TAG, "onReceiveResult (result: " + result + " cme: " +
+                    cme);
+
+            Toast.makeText(HfpTestActivity.this,
+                    resultCodeToString(result, cme),
+                    Toast.LENGTH_SHORT).show();
+        }
+
+        private synchronized void onReceiveAudioStateChange(BluetoothDevice device,
+                int prevState, int state) {
+            Logger.v(TAG, "onReceiveAudioStateChange (" + prevState +
+                    " -> " + state + ")");
+
+            invalidateOptionsMenu();
+        }
+
+        private void onReceiveActionConnectionStateChanged(BluetoothDevice device,
+                int prevState, int state, Bundle features) {
+            Logger.v(TAG, "onReceiveActionConnectionStateChanged: " +
+                    device.getAddress() + " (" +
+                    String.valueOf(prevState) + " -> " +
+                    String.valueOf(state) + ")");
+
+            switch (state) {
+                case BluetoothProfile.STATE_CONNECTED:
+                    updateAgFeatures(features);
+                    Toast.makeText(HfpTestActivity.this, R.string.msg_connection_success,
+                            Toast.LENGTH_SHORT).show();
+
+                    new MonkeyEvent("hfp-connected", true).send();
+
+                    break;
+                case BluetoothProfile.STATE_DISCONNECTED:
+                    new MonkeyEvent("hfp-disconnected", true).send();
+
+                    Toast.makeText(HfpTestActivity.this, R.string.msg_disconnection_success,
+                            Toast.LENGTH_SHORT).show();
+
+                    break;
+            }
+
+            invalidateOptionsMenu();
+        }
+    };
+
+    ///*
+     * HFP Service Connection.
+     ///
+    private final ServiceConnection mHfpServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Logger.v(TAG, "onServiceConnected()");
+            mProfileService = ((ProfileService.LocalBinder) service).getService();
+            mBluetoothHandsfreeClient = mProfileService.getHfpClient();
+
+            int connState = mBluetoothHandsfreeClient.getConnectionState(mDevice);
+
+            if (connState == BluetoothProfile.STATE_CONNECTED) {
+                // trigger refresh of calls list
+                for (BluetoothHandsfreeClientCall call : mBluetoothHandsfreeClient
+                        .getCurrentCalls(mDevice)) {
+                    mCallsListFragment.onCallChanged(call);
+                }
+
+                // get supported AG features
+                updateAgFeatures(mBluetoothHandsfreeClient.getCurrentAgFeatures(mDevice));
+            }
+
+            // trigger refresh of indicators
+            mIndicatorsFragment.onAudioStateChanged(
+                    mBluetoothHandsfreeClient.getAudioState(mDevice), 0);
+            mIndicatorsFragment.onConnStateChanged(connState, 0);
+            mIndicatorsFragment.onAgEvent(mBluetoothHandsfreeClient.getCurrentAgEvents(mDevice));
+
+            invalidateOptionsMenu();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Logger.v(TAG, "onServiceDisconnected()");
+            mProfileService = null;
+            mBluetoothHandsfreeClient = null;
+
+            invalidateOptionsMenu();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Logger.v(TAG, "onCreate()");
+
+        ActivityHelper.initialize(this, R.layout.activity_hfp_test);
+        BluetoothConnectionReceiver.registerObserver(this);
+
+        // bind to app service
+        Intent intent = new Intent(this, ProfileService.class);
+        bindService(intent, mHfpServiceConnection, BIND_AUTO_CREATE);
+
+        prepareActionBar();
+
+        mIndicatorsFragment = (IndicatorsFragment) getFragmentManager().findFragmentById(
+                R.id.indicators);
+        mCallsListFragment = (CallsListFragment) getFragmentManager().findFragmentById(
+                R.id.calls_list);
+        mDialpadFragment = (DialpadFragment) getFragmentManager().findFragmentById(R.id.dialpad);
+
+        setVolumeControlStream(AudioManager.STREAM_BLUETOOTH_SCO);
+    }
+
+    @Override
+    protected void onDestroy() {
+        Logger.v(TAG, "onDestroy");
+
+        super.onDestroy();
+
+        unbindService(mHfpServiceConnection);
+        BluetoothConnectionReceiver.removeObserver(this);
+    }
+
+    @Override
+    protected void onResume() {
+        Logger.v(TAG, "onResume");
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothHandsfreeClient.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothHandsfreeClient.ACTION_AG_EVENT);
+        filter.addAction(BluetoothHandsfreeClient.ACTION_CALL_CHANGED);
+        filter.addAction(BluetoothHandsfreeClient.ACTION_AUDIO_STATE_CHANGED);
+        filter.addAction(BluetoothHandsfreeClient.ACTION_RESULT);
+        filter.addAction(BluetoothHandsfreeClient.ACTION_LAST_VTAG);
+        registerReceiver(mHfpClientReceiver, filter);
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        Logger.v(TAG, "onPause");
+
+        unregisterReceiver(mHfpClientReceiver);
+        super.onPause();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        mActionBarMenu = menu;
+
+        getMenuInflater().inflate(R.menu.menu_hfp_test, menu);
+        menu.findItem(R.id.menu_request_phone_num).setEnabled(mFeatVtag);
+
+        return true;
+    }
+
+    @Override
+    public void onDeviceChanged(BluetoothDevice device) {
+        Logger.v(TAG, "onDeviceChanged()");
+
+        mDevice = device;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_request_phone_num:
+                mBluetoothHandsfreeClient.getLastVoiceTagNumber(mDevice);
+                return true;
+
+            case R.id.menu_call_history:
+                new CallHistoryDialogFragment(mCallHistory).show(getFragmentManager(),
+                        "call-history");
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void onDeviceDisconected() {
+        Logger.v(TAG, "onDeviceDisconected");
+
+        invalidateOptionsMenu();
+    }
+
+    @Override
+    public void onCallHistoryDialogPositive(DialogFragment dialog, String number) {
+        mDialpadFragment.setNumber(number);
+    }
+
+    private void prepareActionBar() {
+        Logger.v(TAG, "prepareActionBar()");
+
+        mActionBar = getActionBar();
+        if (mActionBar != null) {
+            mActionBar.setTitle(R.string.title_hfp_test);
+            mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+        }
+    }
+
+    private String resultCodeToString(int result, int cme) {
+        Logger.v(TAG, "resultCodeToString()");
+
+        switch (result) {
+            case BluetoothHandsfreeClient.ACTION_RESULT_OK:
+                return getString(R.string.hfptest_result_ok_text);
+            case BluetoothHandsfreeClient.ACTION_RESULT_ERROR:
+                return getString(R.string.hfptest_result_error_text);
+            case BluetoothHandsfreeClient.ACTION_RESULT_ERROR_NO_CARRIER:
+                return getString(R.string.hfptest_result_no_carrier_text);
+            case BluetoothHandsfreeClient.ACTION_RESULT_ERROR_BUSY:
+                return getString(R.string.hfptest_result_busy_text);
+            case BluetoothHandsfreeClient.ACTION_RESULT_ERROR_NO_ANSWER:
+                return getString(R.string.hfptest_result_no_answer_text);
+            case BluetoothHandsfreeClient.ACTION_RESULT_ERROR_DELAYED:
+                return getString(R.string.hfptest_result_delayed_text);
+            case BluetoothHandsfreeClient.ACTION_RESULT_ERROR_BLACKLISTED:
+                return getString(R.string.hfptest_result_blacklisted_text);
+            case BluetoothHandsfreeClient.ACTION_RESULT_ERROR_CME:
+                return getString(R.string.hfptest_result_cme_text) + " (" + cme + ")";
+        }
+
+        return getString(R.string.hfptest_call_state_unknown_text);
+    }
+
+    @Override
+    protected void onMonkeyQuery(String op, Bundle params) {
+        if ("getIndicators".equals(op)) {
+            Bundle bundle = mBluetoothHandsfreeClient.getCurrentAgEvents(mDevice);
+
+            MonkeyEvent me = new MonkeyEvent("hfp-ag-event", true);
+
+            if (bundle.containsKey(BluetoothHandsfreeClient.EXTRA_VOICE_RECOGNITION)) {
+                me.addReplyParam("vr", bundle.getInt(BluetoothHandsfreeClient.EXTRA_VOICE_RECOGNITION));
+            }
+
+            if (bundle.containsKey(BluetoothHandsfreeClient.EXTRA_IN_BAND_RING)) {
+                me.addReplyParam("in-band", bundle.getInt(BluetoothHandsfreeClient.EXTRA_IN_BAND_RING));
+            }
+
+            if (bundle.containsKey(BluetoothHandsfreeClient.EXTRA_NETWORK_STATUS)) {
+                me.addReplyParam("network-status",
+                        bundle.getInt(BluetoothHandsfreeClient.EXTRA_NETWORK_STATUS));
+            }
+
+            if (bundle.containsKey(BluetoothHandsfreeClient.EXTRA_NETWORK_ROAMING)) {
+                me.addReplyParam("network-roaming",
+                        bundle.getInt(BluetoothHandsfreeClient.EXTRA_NETWORK_ROAMING));
+            }
+
+            if (bundle.containsKey(BluetoothHandsfreeClient.EXTRA_NETWORK_SIGNAL_STRENGTH)) {
+                me.addReplyParam("signal-strength",
+                        bundle.getInt(BluetoothHandsfreeClient.EXTRA_NETWORK_SIGNAL_STRENGTH));
+            }
+
+            if (bundle.containsKey(BluetoothHandsfreeClient.EXTRA_BATTERY_LEVEL)) {
+                me.addReplyParam("battery-level",
+                        bundle.getInt(BluetoothHandsfreeClient.EXTRA_BATTERY_LEVEL));
+            }
+
+            if (bundle.containsKey(BluetoothHandsfreeClient.EXTRA_OPERATOR_NAME)) {
+                me.addReplyParam("operator-name",
+                        bundle.getString(BluetoothHandsfreeClient.EXTRA_OPERATOR_NAME));
+            }
+
+            if (bundle.containsKey(BluetoothHandsfreeClient.EXTRA_SUBSCRIBER_INFO)) {
+                me.addReplyParam("subscriber-info",
+                        bundle.getString(BluetoothHandsfreeClient.EXTRA_SUBSCRIBER_INFO));
+            }
+
+            me.send();
+        }
+    }
+
+    static String callToJson(BluetoothHandsfreeClientCall call) {
+        JSONObject json = new JSONObject();
+
+        try {
+            json.put("id", call.getId());
+            json.put("number", call.getNumber());
+            json.put("state", callStateToString(call.getState()));
+            json.put("outgoing", call.isOutgoing());
+            json.put("multiparty", call.isMultiParty());
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        return json.toString();
+    }
+
+    static String callStateToString(int state) {
+        switch (state) {
+            case BluetoothHandsfreeClientCall.CALL_STATE_ACTIVE:
+                return "active";
+            case BluetoothHandsfreeClientCall.CALL_STATE_HELD:
+                return "held";
+            case BluetoothHandsfreeClientCall.CALL_STATE_DIALING:
+                return "dialing";
+            case BluetoothHandsfreeClientCall.CALL_STATE_ALERTING:
+                return "alerting";
+            case BluetoothHandsfreeClientCall.CALL_STATE_INCOMING:
+                return "incoming";
+            case BluetoothHandsfreeClientCall.CALL_STATE_WAITING:
+                return "waiting";
+            case BluetoothHandsfreeClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
+                return "held_by_rnh";
+            case BluetoothHandsfreeClientCall.CALL_STATE_TERMINATED:
+                return "terminated";
+        }
+
+        return "unknown";
+    }
+
+    private void updateAgFeatures(Bundle b) {
+        // only supported AG features are being sent
+        mFeatVtag = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT);
+        mFeatVoiceRecognition = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION);
+        mFeatThreeWayCalling = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_3WAY_CALLING);
+        mFeatEnhancedCallControl = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_ECC);
+        mFeatReject = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_REJECT_CALL);
+        mFeatMerge = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_MERGE);
+        mFeatMergeDetach = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH);
+        mFeatReleaseAndAccept = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT);
+        mFeatAcceptHeldOrWaiting = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL);
+        mFeatReleaseHeldOrWaiting = b.containsKey(BluetoothHandsfreeClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL);
+    }
+}*/
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/IBluetoothConnectionObserver.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/IBluetoothConnectionObserver.java
new file mode 100644
index 0000000..dd1df50
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/IBluetoothConnectionObserver.java
@@ -0,0 +1,37 @@
+/*
+ * 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.bttestapp;
+
+import android.bluetooth.BluetoothDevice;
+
+public interface IBluetoothConnectionObserver {
+    public void onDeviceChanged(BluetoothDevice device);
+
+    public void onDeviceDisconected();
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/IndicatorsFragment.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/IndicatorsFragment.java
new file mode 100644
index 0000000..af451b0
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/IndicatorsFragment.java
@@ -0,0 +1,354 @@
+/*
+ * 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.bttestapp;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.bluetooth.BluetoothHandsfreeClient;
+import android.bluetooth.BluetoothProfile;
+import android.os.Bundle;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import com.qualcomm.bttestapp.R;
+*/
+/*public class IndicatorsFragment extends Fragment implements OnClickListener {
+
+    private HfpTestActivity mActivity;
+
+    private ToggleButton mIndConnState;
+
+    private ToggleButton mIndAudioState;
+
+    private ToggleButton mIndVrState;
+
+    private TextView mIndNetworkState;
+
+    private TextView mIndRoamingState;
+
+    private TextView mIndInbandState;
+
+    private TextView mIndSignalLevel;
+
+    private TextView mIndBatteryLevel;
+
+    private TextView mIndOperator;
+
+    private TextView mIndSubscriber;
+
+    private int mDefaultColor;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.indicators_fragment, null);
+
+        mIndConnState = (ToggleButton) view.findViewById(R.id.ind_conn_state);
+        mIndConnState.setOnClickListener(this);
+        mIndAudioState = (ToggleButton) view.findViewById(R.id.ind_audio_state);
+        mIndAudioState.setOnClickListener(this);
+        mIndVrState = (ToggleButton) view.findViewById(R.id.ind_vr_state);
+        mIndVrState.setOnClickListener(this);
+
+        mIndNetworkState = (TextView) view.findViewById(R.id.ind_network_state);
+        mIndRoamingState = (TextView) view.findViewById(R.id.ind_roaming_state);
+        mIndInbandState = (TextView) view.findViewById(R.id.ind_inband_state);
+
+        mIndSignalLevel = (TextView) view.findViewById(R.id.ind_signal_level);
+        mIndBatteryLevel = (TextView) view.findViewById(R.id.ind_battery_level);
+
+        mIndOperator = (TextView) view.findViewById(R.id.ind_operator);
+        mIndSubscriber = (TextView) view.findViewById(R.id.ind_subscriber);
+
+        mDefaultColor = mIndOperator.getTextColors().getDefaultColor();
+
+        resetIndicators(true);
+
+        return view;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mActivity = (HfpTestActivity) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(this.getClass().getSimpleName()
+                    + " can be only attached to " + HfpTestActivity.class.getSimpleName());
+        }
+    }
+
+    public void onConnStateChanged(int state, int prevState) {
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                resetIndicators(false);
+                mIndConnState.setChecked(false);
+                mIndConnState.setEnabled(true);
+
+                mIndAudioState.setEnabled(false);
+                mIndVrState.setEnabled(false);
+                mIndVrState.setChecked(false);
+                break;
+
+            case BluetoothProfile.STATE_CONNECTING:
+                mIndConnState.setChecked(true);
+                mIndConnState.setEnabled(false);
+                break;
+
+            case BluetoothProfile.STATE_DISCONNECTING:
+                mIndConnState.setChecked(false);
+                mIndConnState.setEnabled(false);
+                break;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                mIndConnState.setChecked(true);
+                mIndConnState.setEnabled(true);
+
+                mIndAudioState.setEnabled(true);
+                mIndVrState.setEnabled(mActivity.mFeatVoiceRecognition);
+
+                resetIndicators(false);
+                break;
+        }
+    }
+
+    public void onAudioStateChanged(int state, int prevState) {
+        switch (state) {
+            case BluetoothHandsfreeClient.STATE_AUDIO_DISCONNECTED:
+                mIndAudioState.setChecked(false);
+                mIndAudioState.setEnabled(true);
+                break;
+
+            case BluetoothHandsfreeClient.STATE_AUDIO_CONNECTING:
+                mIndAudioState.setChecked(true);
+                mIndAudioState.setEnabled(false);
+                break;
+
+            case BluetoothHandsfreeClient.STATE_AUDIO_CONNECTED:
+                mIndAudioState.setChecked(true);
+                mIndAudioState.setEnabled(true);
+                break;
+        }
+    }
+
+    public void onAgEvent(Bundle params) {
+        if (params == null) {
+            // it can happen when querying for indicators after connection and
+            // it's ok to ignore
+            return;
+        }
+
+        for (String param : params.keySet()) {
+
+            TextView colorInd = null;
+            // TextView valueInd = null;
+            TextView barInd = null;
+
+            if (param.equals(BluetoothHandsfreeClient.EXTRA_VOICE_RECOGNITION)) {
+
+                boolean enabled = (params.getInt(param) != 0);
+                mIndVrState.setChecked(enabled);
+                mIndVrState.setEnabled(mActivity.mFeatVoiceRecognition);
+
+            } else if (param.equals(BluetoothHandsfreeClient.EXTRA_IN_BAND_RING)) {
+
+                colorInd = mIndInbandState;
+
+            } else if (param.equals(BluetoothHandsfreeClient.EXTRA_OPERATOR_NAME)) {
+
+                setOperator(params.getString(param));
+
+            } else if (param.equals(BluetoothHandsfreeClient.EXTRA_NETWORK_STATUS)) {
+
+                colorInd = mIndNetworkState;
+
+            } else if (param.equals(BluetoothHandsfreeClient.EXTRA_NETWORK_ROAMING)) {
+
+                colorInd = mIndRoamingState;
+
+            } else if (param.equals(BluetoothHandsfreeClient.EXTRA_NETWORK_SIGNAL_STRENGTH)) {
+
+                barInd = mIndSignalLevel;
+
+            } else if (param.equals(BluetoothHandsfreeClient.EXTRA_BATTERY_LEVEL)) {
+
+                barInd = mIndBatteryLevel;
+
+            } else if (param.equals(BluetoothHandsfreeClient.EXTRA_SUBSCRIBER_INFO)) {
+
+                setSubscriber(params.getString(param));
+
+            }
+
+            if (colorInd != null) {
+                if (params.getInt(param, -1) != -1) {
+                    if (params.getInt(param) != 0) {
+                        colorInd.setBackgroundColor(getColor(R.color.ind_on));
+                    } else {
+                        colorInd.setBackgroundColor(getColor(R.color.ind_off));
+                    }
+                    colorInd.setVisibility(View.VISIBLE);
+                } else {
+                    // ignore
+                }
+            }
+
+            // replaced by barInd, but can be used in future
+            // if (valueInd != null) {
+            // if (params.getInt(param, -1) != -1) {
+            // valueInd.setText(Integer.toString(params.getInt(param)));
+            // } else {
+            // valueInd.setText("");
+            // }
+            // }
+
+            if (barInd != null) {
+                int val = params.getInt(param);
+                int color;
+
+                if (val < 2) {
+                    color = getColor(R.color.ind_bar_low);
+                } else if (val < 4) {
+                    color = getColor(R.color.ind_bar_medium);
+                } else {
+                    color = getColor(R.color.ind_bar_full);
+                }
+
+                SpannableString ss = new SpannableString(getResources().getString(R.string.ind_bar));
+                ss.setSpan(new ForegroundColorSpan(color), 0, val, 0);
+                ss.setSpan(new ForegroundColorSpan(getColor(R.color.ind_bar_off)), val,
+                        ss.length(), 0);
+
+                barInd.setText(ss);
+            }
+        }
+    }
+
+    private int getColor(int id) {
+        return getResources().getColor(id);
+    }
+
+    private void resetIndicators(boolean all) {
+        if (all) {
+            mIndConnState.setChecked(false);
+            mIndAudioState.setChecked(false);
+            mIndVrState.setChecked(false);
+
+            mIndConnState.setEnabled(true);
+            mIndAudioState.setEnabled(false);
+            mIndVrState.setEnabled(false);
+        }
+
+        mIndNetworkState.setBackgroundColor(getColor(R.color.ind_off));
+        mIndRoamingState.setBackgroundColor(getColor(R.color.ind_off));
+        mIndInbandState.setBackgroundColor(getColor(R.color.ind_off));
+
+        mIndSignalLevel.setText("");
+        mIndBatteryLevel.setText("");
+
+        setOperator(null);
+        setSubscriber(null);
+    }
+
+    private void setOperator(String text) {
+        if (text != null) {
+            mIndOperator.setText(text);
+            mIndOperator.setTextColor(mDefaultColor);
+        } else {
+            mIndOperator.setText(R.string.ind_operator_unknown);
+            mIndOperator.setTextColor(getColor(R.color.ind_text_unknown));
+        }
+    }
+
+    private void setSubscriber(String text) {
+        if (text != null) {
+            mIndSubscriber.setText(text);
+            mIndSubscriber.setTextColor(mDefaultColor);
+        } else {
+            mIndSubscriber.setText(R.string.ind_subscriber_unknown);
+            mIndSubscriber.setTextColor(getColor(R.color.ind_text_unknown));
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        ///*
+         //* checked state of CompoundButton is toggled automatically when clicked
+         //* so since we need before-click state, it's always opposite of current
+         //* state
+         ///
+        boolean state = !((CompoundButton) view).isChecked();
+
+        switch (view.getId()) {
+            case R.id.ind_conn_state:
+                onClickConnState(state);
+                break;
+
+            case R.id.ind_audio_state:
+                onClickAudioState(state);
+                break;
+
+            case R.id.ind_vr_state:
+                onClickVrState(state);
+                break;
+        }
+    }
+
+    public void onClickConnState(boolean state) {
+        if (state) {
+            mActivity.mBluetoothHandsfreeClient.disconnect(mActivity.mDevice);
+        } else {
+            mActivity.mBluetoothHandsfreeClient.connect(mActivity.mDevice);
+        }
+    }
+
+    public void onClickAudioState(boolean state) {
+        if (state) {
+            mActivity.mBluetoothHandsfreeClient.disconnectAudio();
+        } else {
+            mActivity.mBluetoothHandsfreeClient.connectAudio();
+        }
+    }
+
+    public void onClickVrState(boolean state) {
+        if (state) {
+            mActivity.mBluetoothHandsfreeClient.stopVoiceRecognition(mActivity.mDevice);
+        } else {
+            mActivity.mBluetoothHandsfreeClient.startVoiceRecognition(mActivity.mDevice);
+        }
+        mIndVrState.setEnabled(false);
+    }
+}*/
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MainActivity.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MainActivity.java
new file mode 100644
index 0000000..1a8c85f
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MainActivity.java
@@ -0,0 +1,299 @@
+/*
+ * 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.bttestapp;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMasInstance;
+import android.bluetooth.BluetoothUuid;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import org.codeaurora.bluetooth.bttestapp.R;
+import java.util.ArrayList;
+
+public class MainActivity extends MonkeyActivity {
+
+    private final String TAG = "MainActivity";
+
+    public static final String PREF_DEVICE = "device";
+    public static final String PREF_SERVICES = "services";
+
+    public static final String PICKER_ACTION = "android.bluetooth.devicepicker.action.LAUNCH";
+    public static final String PICKER_SELECTED = "android.bluetooth.devicepicker.action.DEVICE_SELECTED";
+
+    BluetoothDevice mDevice;
+
+    ProfileService mProfileService;
+
+    private boolean mIsBound = false;
+
+    private boolean mDiscoveryInProgress = false;
+
+    private ServicesFragment mServicesFragment = null;
+
+    private final BroadcastReceiver mPickerReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            Log.v(TAG, "mPickerReceiver got " + action);
+
+            if (PICKER_SELECTED.equals(action)) {
+                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                updateDevice(device);
+                unregisterReceiver(this);
+
+                mServicesFragment.removeService(null);
+                mServicesFragment.persistServices();
+            }
+        }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            Log.v(TAG, "mReceiver got " + action);
+
+            if (BluetoothDevice.ACTION_UUID.equals(action)) {
+                BluetoothDevice dev = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                if (!dev.equals(mDevice)) {
+                    return;
+                }
+
+                Parcelable uuids[] = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
+
+                if (uuids != null) {
+                    for (Parcelable uuid : uuids) {
+                        if (BluetoothUuid.PBAP_PSE.equals(uuid)) {
+                            mServicesFragment.addService(ServicesFragment.Service.Type.PBAP, null);
+                        } /*else if (BluetoothUuid.Handsfree_AG.equals(uuid)) {
+                            mServicesFragment.addService(ServicesFragment.Service.Type.HFP, null);
+                        }*/
+                    }
+                }
+
+                mServicesFragment.persistServices();
+
+                if (mDiscoveryInProgress) {
+                    Log.v(TAG, "fetching MAS instances");
+                    mDevice.fetchMasInstances();
+                }
+
+            } else if (BluetoothDevice.ACTION_MAS_INSTANCE.equals(action)) {
+                BluetoothDevice dev = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                if (!dev.equals(mDevice)) {
+                    return;
+                }
+
+                ArrayList<BluetoothMasInstance> insts = intent
+                        .getParcelableArrayListExtra(BluetoothDevice.EXTRA_MAS_INSTANCE);
+
+                if (insts != null) {
+                    mProfileService.setMasInstances(insts);
+
+                    for (BluetoothMasInstance inst : insts) {
+                        mServicesFragment.addService(ServicesFragment.Service.Type.MAP, inst);
+                    }
+                }
+
+                mServicesFragment.persistServices();
+
+                mDiscoveryInProgress = false;
+            }
+        }
+    };
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mProfileService = ((ProfileService.LocalBinder) service).getService();
+            mProfileService.setDevice(mDevice);
+            mServicesFragment.restoreServices();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mProfileService = null;
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Log.v(TAG, "onCreate");
+
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+        setContentView(R.layout.activity_main);
+
+        mServicesFragment = (ServicesFragment) getFragmentManager().findFragmentById(
+                R.id.services_list);
+
+        Intent intent = new Intent(this, ProfileService.class);
+        startService(intent);
+
+        if (bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
+            mIsBound = true;
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        Log.v(TAG, "onStart");
+
+        String addr = getPreferences(MODE_PRIVATE).getString(PREF_DEVICE, null);
+
+        try {
+            BluetoothDevice dev = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(addr);
+            updateDevice(dev);
+        } catch (IllegalArgumentException e) {
+            // just leave device unset
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        Log.v(TAG, "onResume");
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevice.ACTION_UUID);
+        filter.addAction(BluetoothDevice.ACTION_MAS_INSTANCE);
+        registerReceiver(mReceiver, filter);
+
+        if (BluetoothAdapter.getDefaultAdapter().isEnabled() == false) {
+            Button b = (Button) findViewById(R.id.discover_services);
+            b.setEnabled(false);
+
+            b = (Button) findViewById(R.id.select_device);
+            b.setEnabled(false);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        Log.v(TAG, "onPause");
+
+        unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        Log.v(TAG, "onStop");
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (mIsBound) {
+            unbindService(mConnection);
+        }
+
+        Log.v(TAG, "onDestroy");
+    }
+
+    public void onButtonClick(View v) {
+        if (v.getId() == R.id.select_device) {
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(PICKER_SELECTED);
+            registerReceiver(mPickerReceiver, filter);
+
+            Intent intent = new Intent(PICKER_ACTION);
+            intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            startActivity(intent);
+
+        } else if (v.getId() == R.id.discover_services) {
+            if (mDevice == null || mDiscoveryInProgress) {
+                return;
+            }
+
+            mServicesFragment.removeService(null);
+
+            Log.v(TAG, "fetching UUIDs");
+            mDiscoveryInProgress = mDevice.fetchUuidsWithSdp();
+        }
+    }
+
+    private void updateDevice(BluetoothDevice device) {
+        SharedPreferences.Editor prefs = getPreferences(MODE_PRIVATE).edit();
+
+        TextView name = (TextView) findViewById(R.id.device_name);
+        TextView addr = (TextView) findViewById(R.id.device_address);
+
+        if (device == null) {
+            name.setText(R.string.blank);
+            addr.setText(R.string.blank);
+
+            prefs.remove(PREF_DEVICE);
+        } else {
+            Intent intent = new Intent(BluetoothConnectionReceiver.ACTION_NEW_BLUETOOTH_DEVICE);
+            intent.putExtra(BluetoothConnectionReceiver.EXTRA_DEVICE_ADDRESS, device.getAddress());
+            sendBroadcast(intent);
+
+            name.setText(device.getName());
+            addr.setText(device.getAddress());
+
+            prefs.putString(PREF_DEVICE, device.getAddress());
+        }
+
+        prefs.commit();
+
+        mDevice = device;
+
+        if (mProfileService != null) {
+            mProfileService.setDevice(mDevice);
+        }
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MapTestActivity.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MapTestActivity.java
new file mode 100644
index 0000000..1aa5165
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MapTestActivity.java
@@ -0,0 +1,1446 @@
+/*
+ * 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.bttestapp;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentTransaction;
+import android.bluetooth.BluetoothMasInstance;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.TimePicker;
+import android.widget.Toast;
+import android.widget.ViewFlipper;
+
+import com.android.vcard.VCardConstants;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardProperty;
+import org.codeaurora.bluetooth.bttestapp.R;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapBmessage;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapEventReport;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapMessage;
+import org.codeaurora.bluetooth.mapclient.BluetoothMasClient;
+import org.codeaurora.bluetooth.mapclient.BluetoothMasClient.CharsetType;
+import org.codeaurora.bluetooth.mapclient.BluetoothMasClient.MessagesFilter;
+import org.codeaurora.bluetooth.bttestapp.GetTextDialogFragment.GetTextDialogListener;
+import org.codeaurora.bluetooth.bttestapp.R;
+import org.codeaurora.bluetooth.bttestapp.StringListDialogFragment.StringListDialogListener;
+import org.codeaurora.bluetooth.bttestapp.services.IMapServiceCallback;
+import org.codeaurora.bluetooth.bttestapp.util.Logger;
+import org.codeaurora.bluetooth.bttestapp.util.MonkeyEvent;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+public class MapTestActivity extends MonkeyActivity implements GetTextDialogListener,
+        StringListDialogListener {
+
+    private final String TAG = "MapTestActivity";
+
+    private final static short MAX_LIST_COUNT_DEFAULT = 0;
+    private final static short LIST_START_OFFSET_DEFAULT = 0;
+    private final static byte SUBJECT_LENGTH_DEFAULT = 0;
+    private final static String MESSAGES_FILTER_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    private static final int REQUEST_CODE_GET_PARAMETERS = 0;
+
+    private final String TAB_LIST = "List";
+    private final String TAB_PREVIEW = "Preview";
+    private final String TAB_EDIT = "Edit";
+
+    private int mMasInstanceId = -1;
+
+    private String mCurrentTab = TAB_LIST;
+
+    private final String[] mActionBarTabsNames = {
+            TAB_LIST, TAB_PREVIEW, TAB_EDIT
+    };
+
+    enum Job {
+        IDLE,
+        CONNECT,
+        DISCONNECT,
+        UPDATE_INBOX,
+        SET_PATH,
+        GET_MESSAGE_LISTING,
+        GET_MESSAGE_LISTING_SIZE,
+        GET_FOLDER_LISTING,
+        GET_FOLDER_LISTING_SIZE,
+        GET_MESSAGE,
+        SET_STATUS_READ,
+        SET_STATUS_UNREAD,
+        DELETE_MESSAGE,
+        PUSH_MESSAGE;
+    }
+
+    private Job mCurrentJob = Job.IDLE;
+
+    private String mStartingGetMessageHandle = null;
+
+    private String mPendingGetMessageHandle = null;
+
+    private ArrayDeque<String> mSetPathQueue = null;
+
+    private int mMessageListingParameters = 0;
+
+    private ActionBar mActionBar = null;
+
+    private ViewFlipper mViewFlipper = null;
+
+    private String mPreviewMsgHandle = null;
+    private BluetoothMapBmessage mMapBmessage = null;
+
+    // UI on List tab
+    private TextView mTextViewCurrentFolder = null;
+    private Spinner mSpinnerFolders = null;
+    private ArrayAdapter<String> mAdapterFolders = null;
+    private EditText mEditTextMaxListCountFolders = null;
+    private EditText mEditTextListStartOffsetFolders = null;
+    private EditText mEditTextMaxListCountMessages = null;
+    private EditText mEditTextListStartOffsetMessages = null;
+    private ListView mListViewMessages = null;
+    private EditText mEditTextSubjectLength = null;
+
+    private List<BluetoothMapMessage> mModelMessages = null;
+    private BluetoothMapMessageAdapter mAdapterMessages = null;
+
+    // UI on Preview/Edit tab
+    private EditText mViewBmsgHandle = null;
+    private EditText mViewBmsgStatus = null;
+    private EditText mViewBmsgType = null;
+    private EditText mViewBmsgFolder = null;
+    private EditText mViewBmsgEncoding = null;
+    private EditText mViewBmsgCharset = null;
+    private EditText mViewBmsgLanguage = null;
+    private EditText mViewBmsgContents = null;
+    private EditText mViewBmsgOrig = null;
+    private EditText mViewBmsgRcpt = null;
+
+    // MessagesFilter parameters
+    private byte mMessageType = MessagesFilter.MESSAGE_TYPE_ALL;
+    private Date mPeriodBegin = null;
+    private Date mPeriodEnd = null;
+    private byte mReadStatus = MessagesFilter.READ_STATUS_ANY;
+    private String mRecipient = null;
+    private String mOriginator = null;
+    private byte mPriority = MessagesFilter.PRIORITY_ANY;
+
+    private ArrayList<View> mListTouchables = null;
+
+    private final SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(MESSAGES_FILTER_DATE_FORMAT);
+
+    private ProfileService mProfileService = null;
+
+    private final ArrayList<String> mEditOriginators = new ArrayList<String>();
+
+    private final ArrayList<String> mEditRecipients = new ArrayList<String>();
+
+    private final ServiceConnection mMapServiceConnection = new ServiceConnection() {
+
+        private void shortToast(String s) {
+            Toast.makeText(MapTestActivity.this, s, Toast.LENGTH_SHORT).show();
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mProfileService = ((ProfileService.LocalBinder) service).getService();
+
+            BluetoothMasInstance inst = mProfileService.getMapClient(mMasInstanceId)
+                    .getInstanceData();
+            MapTestActivity.this.getActionBar().setSubtitle(inst.getName());
+
+            mProfileService.setMapCallback(mMasInstanceId, new IMapServiceCallback() {
+
+                @Override
+                public void onConnect() {
+                    goToState(Job.IDLE);
+                    shortToast("MAS connect OK");
+                }
+
+                @Override
+                public void onConnectError() {
+                    goToState(Job.IDLE);
+                    shortToast("MAS connect FAILED");
+                }
+
+                @Override
+                public void onUpdateInbox() {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-updateinbox", true).send();
+                    shortToast("UpdateInbox OK");
+                }
+
+                @Override
+                public void onUpdateInboxError() {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-updateinbox", false).send();
+                    shortToast("UpdateInbox FAILED");
+                }
+
+                @Override
+                public void onSetPath(String path) {
+                    if (mSetPathQueue != null && mSetPathQueue.size() > 0) {
+                        String next = mSetPathQueue.removeFirst();
+                        mProfileService.getMapClient(mMasInstanceId).setFolderDown(next);
+                    } else {
+                        mTextViewCurrentFolder.setText(path);
+                        clearFolderList();
+
+                        goToState(Job.IDLE);
+                        new MonkeyEvent("map-setpath", true).addReplyParam("path", path)
+                                .send();
+                        shortToast("SetPath OK: path=" + path);
+                    }
+                }
+
+                @Override
+                public void onSetPathError(String path) {
+                    if (mSetPathQueue != null) {
+                        mSetPathQueue = null;
+                        mTextViewCurrentFolder.setText(path);
+                        clearFolderList();
+                    }
+
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-setpath", false).addReplyParam("path", path)
+                            .send();
+                    shortToast("SetPath FAILED: path=" + path);
+                }
+
+                @Override
+                public void onGetMessagesListing(ArrayList<BluetoothMapMessage> messages) {
+                    mAdapterMessages.clear();
+                    mAdapterMessages.addAll(messages);
+
+                    updateListEmptyView(false);
+
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-messageslisting", true)
+                            .addReplyParam("size", messages.size()).addExtReply(messages).send();
+                    shortToast("GetMessagesListing OK: size=" + messages.size());
+                }
+
+                @Override
+                public void onGetMessagesListingError() {
+                    updateListEmptyView(false);
+
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-messageslisting", false).send();
+                    shortToast("GetMessagesListing FAILED");
+                }
+
+                @Override
+                public void onGetFolderListingSize(int size) {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-getfolderlisting-size", true)
+                            .addReplyParam("size", size)
+                            .send();
+                    shortToast("GetMessagesListing size OK: size=" + size);
+                }
+
+                @Override
+                public void onGetFolderListingSizeError() {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-getfolderlisting-size", false).send();
+                    shortToast("GetMessagesListing size FAILED");
+                }
+
+                @Override
+                public void onGetFolderListing(ArrayList<String> folders) {
+                    clearFolderList();
+                    mAdapterFolders.addAll(folders);
+
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-getfolderlisting", true)
+                            .addReplyParam("size", folders.size()).addExtReply(folders).send();
+                    shortToast("GetFolderListing OK: size=" + folders.size());
+                }
+
+                @Override
+                public void onGetFolderListingError() {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-getfolderlisting", false).send();
+                    shortToast("GetFolderListing FAILED");
+                }
+
+                @Override
+                public void onGetMessage(BluetoothMapBmessage message) {
+                    updateMessage(message);
+
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-getmessage", true)
+                            .addExtReply(message.toString()).send();
+                    shortToast("GetMessage OK");
+                }
+
+                @Override
+                public void onGetMessageError() {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-getmessage", false).send();
+                    shortToast("GetMessage FAILED");
+                }
+
+                @Override
+                public void onSetMessageStatus() {
+                    switch (mCurrentJob) {
+                        case SET_STATUS_READ:
+                            /* TODO: re-read message? check spec */
+                            break;
+                        case SET_STATUS_UNREAD:
+                            /* TODO: re-read message? check spec */
+                            break;
+                        case DELETE_MESSAGE:
+                            resetPreviewEditUi();
+                            Toast.makeText(MapTestActivity.this, "Message deleted!",
+                                    Toast.LENGTH_SHORT).show();
+                            break;
+                        default:
+                            break;
+                    }
+
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-setmessagestatus", true).send();
+                    shortToast("SetMessageStatus OK");
+                }
+
+                @Override
+                public void onSetMessageStatusError() {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-setmessagestatus", false).send();
+                    shortToast("SetMessageStatus FAILED");
+                }
+
+                @Override
+                public void onPushMessage(String handle) {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-pushmessage", true).addReplyParam("handle", handle)
+                            .send();
+                    shortToast("PushMessage OK: handle=" + handle);
+                }
+
+                @Override
+                public void onPushMessageError() {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-pushmessage", false).send();
+                    shortToast("PushMessage FAILED");
+                }
+
+                @Override
+                public void onGetMessagesListingSize(int size) {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-getmessageslisting-size", true)
+                            .addReplyParam("size", size).send();
+                    shortToast("GetMessagesListing size OK: size=" + size);
+                }
+
+                @Override
+                public void onGetMessagesListingSizeError() {
+                    goToState(Job.IDLE);
+                    new MonkeyEvent("map-getmessageslisting-size", false).send();
+                    shortToast("GetMessagesListing size FAILED");
+                }
+
+                @Override
+                public void onEventReport(BluetoothMapEventReport eventReport) {
+                    String msgType = (eventReport.getMsgType() != null) ?
+                            eventReport.getMsgType().toString() : getString(R.string.blank);
+
+                    new MonkeyEvent("map-eventreport", true)
+                            .addReplyParam("type", eventReport.getType().toString())
+                            .addReplyParam("handle", eventReport.getHandle())
+                            .addReplyParam("folder", eventReport.getFolder())
+                            .addReplyParam("old_folder", eventReport.getOldFolder())
+                            .addReplyParam("msg_type", msgType)
+                            .send();
+                }
+            });
+
+            ProfileService.MapSessionData map = mProfileService.getMapSessionData(mMasInstanceId);
+            if (map != null) {
+                if (map.getFolderListing != null) {
+                    clearFolderList();
+                    mAdapterFolders.addAll(map.getFolderListing);
+                }
+
+                if (map.getMessagesListing != null) {
+                    mAdapterMessages.clear();
+                    mAdapterMessages.addAll(map.getMessagesListing);
+                    updateListEmptyView(false);
+                }
+
+                if (map.getMessage != null) {
+                    updateMessage(map.getMessage);
+                }
+            }
+
+            mTextViewCurrentFolder.setText(mProfileService.getMapClient(mMasInstanceId)
+                    .getCurrentPath());
+
+            updateUi(true);
+
+            if (mStartingGetMessageHandle != null) {
+                getMessage(mStartingGetMessageHandle, CharsetType.UTF_8, false);
+                mStartingGetMessageHandle = null;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mProfileService = null;
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+
+        ActivityHelper.initialize(this, R.layout.activity_map_test);
+        ActivityHelper.setActionBarTitle(this, R.string.title_map_test);
+
+        mMasInstanceId = intent.getIntExtra(ProfileService.EXTRA_MAP_INSTANCE_ID, -1);
+
+        if (ProfileService.ACTION_MAP_GET_MESSAGE.equals(intent.getAction())) {
+            mStartingGetMessageHandle = intent
+                    .getStringExtra(ProfileService.EXTRA_MAP_MESSAGE_HANDLE);
+        }
+
+        if (mMasInstanceId < 0) {
+            Log.e(TAG, "Cannot start MAP activity without instance information");
+            finish();
+        }
+
+        intent = new Intent(MapTestActivity.this, ProfileService.class);
+        bindService(intent, mMapServiceConnection, BIND_AUTO_CREATE);
+
+        mViewFlipper = (ViewFlipper) findViewById(R.id.maptest_viewflipper);
+        mTextViewCurrentFolder = (TextView) findViewById(R.id.maptest_nav_current);
+        mSpinnerFolders = (Spinner) findViewById(R.id.maptest_nav_folders);
+        mAdapterFolders = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
+        mSpinnerFolders.setAdapter(mAdapterFolders);
+        mEditTextMaxListCountFolders = (EditText) findViewById(R.id.maptest_nav_list_max);
+        mEditTextMaxListCountFolders.setText(String.valueOf(MAX_LIST_COUNT_DEFAULT));
+        mEditTextListStartOffsetFolders = (EditText) findViewById(R.id.maptest_nav_list_offset);
+        mEditTextListStartOffsetFolders.setText(String.valueOf(LIST_START_OFFSET_DEFAULT));
+        mEditTextMaxListCountMessages = (EditText) findViewById(R.id.maptest_msglist_max);
+        mEditTextMaxListCountMessages.setText(String.valueOf(MAX_LIST_COUNT_DEFAULT));
+        mEditTextListStartOffsetMessages = (EditText) findViewById(R.id.maptest_msglist_offset);
+        mEditTextListStartOffsetMessages.setText(String.valueOf(LIST_START_OFFSET_DEFAULT));
+        mListViewMessages = (ListView) findViewById(R.id.maptest_msglist_lv);
+        mListViewMessages.setEmptyView(findViewById(R.id.maptest_msglist_empty));
+        mModelMessages = new ArrayList<BluetoothMapMessage>();
+        mAdapterMessages = new BluetoothMapMessageAdapter();
+        mListViewMessages.setAdapter(mAdapterMessages);
+        mListViewMessages.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                String handle = mModelMessages.get(position).getHandle();
+
+                getMessage(handle, CharsetType.UTF_8, false);
+            }
+        });
+        registerForContextMenu(mListViewMessages);
+
+        mEditTextSubjectLength = (EditText) findViewById(R.id.maptest_msglist_subject_len);
+        mEditTextSubjectLength.setText(String.valueOf(SUBJECT_LENGTH_DEFAULT));
+        mViewBmsgHandle = (EditText) findViewById(R.id.map_msg_handle);
+        mViewBmsgStatus = (EditText) findViewById(R.id.maptest_bmsg_status);
+        mViewBmsgType = (EditText) findViewById(R.id.maptest_bmsg_type);
+        mViewBmsgFolder = (EditText) findViewById(R.id.maptest_bmsg_folder);
+        mViewBmsgEncoding = (EditText) findViewById(R.id.maptest_bbody_encoding);
+        mViewBmsgCharset = (EditText) findViewById(R.id.maptest_bbody_charset);
+        mViewBmsgLanguage = (EditText) findViewById(R.id.maptest_bbody_language);
+        mViewBmsgContents = (EditText) findViewById(R.id.maptest_message);
+        mViewBmsgOrig = (EditText) findViewById(R.id.maptest_orig);
+        mViewBmsgRcpt = (EditText) findViewById(R.id.maptest_rcpt);
+
+        clearFolderList();
+        updateListEmptyView(false);
+
+        mActionBar = getActionBar();
+        mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+        ActionBar.TabListener tabListener = new ActionBar.TabListener() {
+            @Override
+            public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+            }
+
+            @Override
+            public void onTabSelected(Tab tab, FragmentTransaction ft) {
+                mCurrentTab = tab.getText().toString();
+
+                if (mCurrentTab.equals(TAB_LIST)) {
+                    mViewFlipper.setDisplayedChild(0);
+                } else if (mCurrentTab.equals(TAB_PREVIEW)) {
+                    mViewFlipper.setDisplayedChild(1);
+                } else if (mCurrentTab.equals(TAB_EDIT)) {
+                    mViewFlipper.setDisplayedChild(2);
+                }
+
+                invalidateOptionsMenu();
+            }
+
+            @Override
+            public void onTabReselected(Tab tab, FragmentTransaction ft) {
+            }
+        };
+
+        for (String tab : mActionBarTabsNames) {
+            mActionBar.addTab(
+                    mActionBar.newTab()
+                            .setText(tab)
+                            .setTabListener(tabListener));
+
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode == REQUEST_CODE_GET_PARAMETERS) {
+            if (resultCode == RESULT_OK) {
+                mMessageListingParameters = (int) data.getLongExtra("result", 0);
+            }
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        Logger.v(TAG, "onStart()");
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        Logger.v(TAG, "onStop()");
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        Logger.v(TAG, "onDestroy()");
+        if (mProfileService != null) {
+            mProfileService.setMapCallback(mMasInstanceId, null);
+        }
+        unbindService(mMapServiceConnection);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        mActionBarMenu = menu;
+
+        getMenuInflater().inflate(R.menu.menu_map_test, menu);
+
+        boolean connected = false;
+
+        if (mProfileService != null) {
+            connected = (mProfileService.getMapClient(mMasInstanceId).getState() == BluetoothMasClient.ConnectionState.CONNECTED);
+        }
+
+        menu.findItem(R.id.menu_map_connect).setVisible(!connected);
+        menu.findItem(R.id.menu_map_disconnect).setVisible(connected);
+        menu.findItem(R.id.menu_map_update_inbox).setVisible(connected);
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_map_connect:
+                onClickConnect();
+                break;
+            case R.id.menu_map_disconnect:
+                onClickDisconnect();
+                break;
+            case R.id.menu_map_update_inbox:
+                onClickUpdateInbox();
+                break;
+            case R.id.menu_map_goto_inbox:
+                goToFolder("telecom/msg/inbox");
+                break;
+            case R.id.menu_map_goto_outbox:
+                goToFolder("telecom/msg/outbox");
+                break;
+            case R.id.menu_map_goto_draft:
+                goToFolder("telecom/msg/draft");
+                break;
+            default:
+                Logger.w(TAG, "Unknown item selected.");
+                break;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void clearFolderList() {
+        mAdapterFolders.clear();
+        mAdapterFolders.add(".");
+    }
+
+    private void onClickConnect() {
+        mProfileService.getMapClient(mMasInstanceId).connect();
+        goToState(Job.CONNECT);
+    }
+
+    private void onClickDisconnect() {
+        mProfileService.getMapClient(mMasInstanceId).disconnect();
+        goToState(Job.DISCONNECT);
+    }
+
+    private void onClickUpdateInbox() {
+        mProfileService.getMapClient(mMasInstanceId).updateInbox();
+        goToState(Job.UPDATE_INBOX);
+    }
+
+    private VCardEntry createVcard(BluetoothMapBmessage.Type type, String val) {
+        VCardEntry vcard = new VCardEntry();
+        VCardProperty prop = new VCardProperty();
+
+        prop.setName(VCardConstants.PROPERTY_N);
+        prop.setValues(val);
+        vcard.addProperty(prop);
+
+        if (BluetoothMapBmessage.Type.EMAIL.equals(type)) {
+            prop.setName(VCardConstants.PROPERTY_EMAIL);
+        } else {
+            prop.setName(VCardConstants.PROPERTY_TEL);
+        }
+        vcard.addProperty(prop);
+
+        return vcard;
+    }
+
+    public void onClickPushMessage(View v) {
+        boolean transparent = ((CheckBox) findViewById(R.id.map_msg_push_transparent)).isChecked();
+        boolean retry = ((CheckBox) findViewById(R.id.map_msg_push_retry)).isChecked();
+        Spinner typeView = (Spinner) findViewById(R.id.bmsgedit_type);
+        Spinner encView = (Spinner) findViewById(R.id.bmsgedit_encoding);
+        EditText contentsView = (EditText) findViewById(R.id.bmsgedit_contents);
+        int charset = ((RadioGroup) findViewById(R.id.map_msg_push_charset))
+                .getCheckedRadioButtonId();
+
+        BluetoothMapBmessage bmsg = new BluetoothMapBmessage();
+
+        bmsg.setStatus(BluetoothMapBmessage.Status.UNREAD);
+
+        for (BluetoothMapBmessage.Type t : BluetoothMapBmessage.Type.values()) {
+            if (t.name().equals(typeView.getSelectedItem().toString())) {
+                bmsg.setType(t);
+                break;
+            }
+        }
+
+        if (charset != R.id.map_msg_push_charset_native) {
+            bmsg.setCharset("UTF-8");
+        }
+
+        if (encView.getSelectedItemPosition() > 0) {
+            bmsg.setEncoding(encView.getSelectedItem().toString());
+        }
+
+        bmsg.setFolder(mProfileService.getMapClient(mMasInstanceId).getCurrentPath());
+
+        for (String rcpt : mEditOriginators) {
+            bmsg.addRecipient(createVcard(bmsg.getType(), rcpt));
+        }
+
+        for (String orig : mEditRecipients) {
+            bmsg.addRecipient(createVcard(bmsg.getType(), orig));
+        }
+
+        bmsg.setBodyContent(contentsView.getText().toString());
+
+        mProfileService
+                .getMapClient(mMasInstanceId)
+                .pushMessage(
+                        null,
+                        bmsg,
+                        charset == R.id.map_msg_push_charset_native ? BluetoothMasClient.CharsetType.NATIVE
+                                : BluetoothMasClient.CharsetType.UTF_8, transparent, retry);
+
+        goToState(Job.PUSH_MESSAGE);
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        int instanceId = intent.getIntExtra(ProfileService.EXTRA_MAP_INSTANCE_ID, -1);
+
+        if (instanceId < 0) {
+            // don't care if instance info is missing
+            return;
+        }
+
+        // in case request is for different MAS instance than activity is
+        // running, we'll just restart activity using the same intent to get
+        // proper MAS instance
+        if (instanceId != mMasInstanceId) {
+            finish();
+            overridePendingTransition(0, 0);
+            startActivity(intent);
+            overridePendingTransition(0, 0);
+            return;
+        }
+
+        if (ProfileService.ACTION_MAP_GET_MESSAGE.equals(intent.getAction())) {
+            String handle = intent.getStringExtra(ProfileService.EXTRA_MAP_MESSAGE_HANDLE);
+            getMessage(handle, CharsetType.UTF_8, false);
+        }
+    }
+
+    public void onClickDeleteMessage(View view) {
+        if (mPreviewMsgHandle != null) {
+            mProfileService.getMapClient(mMasInstanceId).setMessageDeletedStatus(mPreviewMsgHandle,
+                    true);
+            goToState(Job.DELETE_MESSAGE);
+        }
+    }
+
+    public void onClickSetStatus(View view) {
+        if (mMapBmessage == null || mPreviewMsgHandle == null) {
+            return;
+        }
+
+        if (mMapBmessage.getStatus().equals(BluetoothMapBmessage.Status.READ)) {
+            mProfileService.getMapClient(mMasInstanceId).setMessageReadStatus(mPreviewMsgHandle,
+                    false);
+            goToState(Job.SET_STATUS_UNREAD);
+        } else {
+            mProfileService.getMapClient(mMasInstanceId).setMessageReadStatus(mPreviewMsgHandle,
+                    true);
+            goToState(Job.SET_STATUS_READ);
+        }
+    }
+
+    public void onClickSetPathRoot(View view) {
+        mProfileService.getMapClient(mMasInstanceId).setFolderRoot();
+        goToState(Job.SET_PATH);
+    }
+
+    public void onClickSetPathUp(View view) {
+        mProfileService.getMapClient(mMasInstanceId).setFolderUp();
+        goToState(Job.SET_PATH);
+    }
+
+    public void onClickSetPathEnter(View view) {
+        String folder = mSpinnerFolders.getSelectedItem().toString();
+
+        // do not let go to current folder
+        if (folder.equals(".")) {
+            return;
+        }
+
+        mProfileService.getMapClient(mMasInstanceId).setFolderDown(folder);
+        goToState(Job.SET_PATH);
+    }
+
+    public void onClickGetFolderListing(View view) {
+        int count = Integer.parseInt(mEditTextMaxListCountFolders.getText().toString());
+        int offset = Integer.parseInt(mEditTextListStartOffsetFolders.getText().toString());
+
+        try {
+            mProfileService.getMapClient(mMasInstanceId).getFolderListing(count, offset);
+            goToState(Job.GET_FOLDER_LISTING);
+        } catch (IllegalArgumentException e) {
+            Toast.makeText(this,
+                    "GetFolderListing FAILED: illegal arguments (" + e.getMessage() + ")",
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    public void onClickGetFolderListingSize(View view) {
+        mProfileService.getMapClient(mMasInstanceId).getFolderListingSize();
+        goToState(Job.GET_FOLDER_LISTING_SIZE);
+    }
+
+    public void onClickMessageParameters(View view) {
+        Intent intent = new Intent(MapTestActivity.this, MessageFilterActivity.class);
+        intent.putExtra("filter", (long) mMessageListingParameters);
+        startActivityForResult(intent, REQUEST_CODE_GET_PARAMETERS);
+    }
+
+    public MessagesFilter getLocalMessageFilter() {
+        MessagesFilter messageFilter = new MessagesFilter();
+        messageFilter.setMessageType(mMessageType);
+        messageFilter.setOriginator(mOriginator);
+        messageFilter.setPeriod(mPeriodBegin, mPeriodEnd);
+        messageFilter.setPriority(mPriority);
+        messageFilter.setReadStatus(mReadStatus);
+        messageFilter.setRecipient(mRecipient);
+        return messageFilter;
+    }
+
+    public class MessageFilterDialogFragment extends DialogFragment {
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            View dialogView = MapTestActivity.this.getLayoutInflater().inflate(
+                    R.layout.messages_filter, null);
+
+            final MessageFilterHolder holder = new MessageFilterHolder(dialogView);
+            holder.populate();
+
+            final AlertDialog.Builder alertBuilder = new AlertDialog.Builder(MapTestActivity.this);
+            alertBuilder.setView(dialogView);
+            alertBuilder
+                    .setTitle(getResources().getString(R.string.dialog_title_create_msg_filter));
+            alertBuilder.setPositiveButton(android.R.string.ok,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            holder.save();
+                        }
+                    });
+            alertBuilder.setNegativeButton(android.R.string.cancel, null);
+            alertBuilder.setNeutralButton(getResources().getString(R.string.clear), null);
+
+            // After click on button with assigned listener in function
+            // setNeutralButton, the dialog automatically will close. To avoid
+            // it,
+            // onClickListener for this button have to be assigned manually.
+            final AlertDialog alertDialog = alertBuilder.create();
+            alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
+                @Override
+                public void onShow(DialogInterface dialog) {
+                    Button clearButton = alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL);
+                    clearButton.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            holder.clear();
+                        }
+                    });
+                }
+            });
+
+            return alertDialog;
+        }
+    }
+
+    public void onClickGetMessagesFilter(View view) {
+        new MessageFilterDialogFragment().show(getFragmentManager(), "msg_filter");
+    }
+
+    class MessageFilterHolder {
+        CheckBox type_sms_gsm = null;
+        CheckBox type_sms_cdma = null;
+        CheckBox type_email = null;
+        CheckBox type_mms = null;
+        RadioButton status_read_all = null;
+        RadioButton status_unread = null;
+        RadioButton status_read = null;
+        RadioButton priority_all = null;
+        RadioButton priority_high = null;
+        RadioButton priority_non_high = null;
+        EditText period_begin = null;
+        EditText period_end = null;
+        EditText recipient = null;
+        EditText originator = null;
+        ImageButton pickPeriodBegin = null;
+        ImageButton pickPeriodEnd = null;
+
+        MessageFilterHolder(View row) {
+            type_sms_gsm = (CheckBox) row.findViewById(R.id.map_filter_type_sms_gsm);
+            type_sms_cdma = (CheckBox) row.findViewById(R.id.map_filter_type_sms_cdma);
+            type_email = (CheckBox) row.findViewById(R.id.map_filter_type_email);
+            type_mms = (CheckBox) row.findViewById(R.id.map_filter_type_mms);
+            status_read_all = (RadioButton) row.findViewById(R.id.map_filter_read_status_all);
+            status_unread = (RadioButton) row.findViewById(R.id.map_filter_read_status_unread);
+            status_read = (RadioButton) row.findViewById(R.id.map_filter_read_status_read);
+            priority_all = (RadioButton) row.findViewById(R.id.map_filter_priority_all);
+            priority_high = (RadioButton) row.findViewById(R.id.map_filter_priority_high);
+            priority_non_high = (RadioButton) row.findViewById(R.id.map_filter_status_non_high);
+            period_begin = (EditText) row.findViewById(R.id.map_filter_period_begin);
+            period_end = (EditText) row.findViewById(R.id.map_filter_period_end);
+            recipient = (EditText) row.findViewById(R.id.map_filter_recipient);
+            originator = (EditText) row.findViewById(R.id.map_filter_originator);
+            pickPeriodBegin = (ImageButton) row.findViewById(R.id.map_pick_data_period_begin);
+            pickPeriodBegin.setOnClickListener(new View.OnClickListener() {
+
+                @Override
+                public void onClick(View v) {
+                    new DateTimePicker(period_begin).pick();
+                }
+            });
+            pickPeriodEnd = (ImageButton) row.findViewById(R.id.map_pick_data_period_end);
+            pickPeriodEnd.setOnClickListener(new View.OnClickListener() {
+
+                @Override
+                public void onClick(View v) {
+                    new DateTimePicker(period_end).pick();
+                }
+            });
+        }
+
+        void populate() {
+            type_sms_gsm.setChecked((mMessageType & MessagesFilter.MESSAGE_TYPE_SMS_GSM) != 0);
+            type_sms_cdma.setChecked((mMessageType & MessagesFilter.MESSAGE_TYPE_SMS_CDMA) != 0);
+            type_email.setChecked((mMessageType & MessagesFilter.MESSAGE_TYPE_EMAIL) != 0);
+            type_mms.setChecked((mMessageType & MessagesFilter.MESSAGE_TYPE_MMS) != 0);
+            status_read_all.setChecked(mReadStatus == MessagesFilter.READ_STATUS_ANY);
+            status_read.setChecked(mReadStatus == MessagesFilter.READ_STATUS_READ);
+            status_unread.setChecked(mReadStatus == MessagesFilter.READ_STATUS_UNREAD);
+            priority_all.setChecked(mPriority == MessagesFilter.PRIORITY_ANY);
+            priority_high.setChecked(mPriority == MessagesFilter.PRIORITY_HIGH);
+            priority_non_high.setChecked(mPriority == MessagesFilter.PRIORITY_NON_HIGH);
+
+            if (mPeriodBegin != null) {
+                period_begin.setText(mSimpleDateFormat.format(mPeriodBegin));
+            } else {
+                period_begin.setText(null);
+            }
+
+            if (mPeriodEnd != null) {
+                period_end.setText(mSimpleDateFormat.format(mPeriodEnd));
+            } else {
+                period_end.setText(null);
+            }
+
+            recipient.setText(mRecipient);
+            originator.setText(mOriginator);
+        }
+
+        void clear() {
+            mMessageType = MessagesFilter.MESSAGE_TYPE_ALL;
+            mPeriodBegin = null;
+            mPeriodEnd = null;
+            mReadStatus = MessagesFilter.READ_STATUS_ANY;
+            mRecipient = null;
+            mOriginator = null;
+            mPriority = MessagesFilter.PRIORITY_ANY;
+
+            populate();
+        }
+
+        void save() {
+            mMessageType = MessagesFilter.MESSAGE_TYPE_ALL;
+
+            if (type_sms_gsm.isChecked()) {
+                mMessageType |= MessagesFilter.MESSAGE_TYPE_SMS_GSM;
+            }
+
+            if (type_sms_cdma.isChecked()) {
+                mMessageType |= MessagesFilter.MESSAGE_TYPE_SMS_CDMA;
+            }
+
+            if (type_email.isChecked()) {
+                mMessageType |= MessagesFilter.MESSAGE_TYPE_EMAIL;
+            }
+
+            if (type_mms.isChecked()) {
+                mMessageType |= MessagesFilter.MESSAGE_TYPE_MMS;
+            }
+
+            if (status_read.isChecked()) {
+                mReadStatus = MessagesFilter.READ_STATUS_READ;
+            } else if (status_unread.isChecked()) {
+                mReadStatus = MessagesFilter.READ_STATUS_UNREAD;
+            } else {
+                mReadStatus = MessagesFilter.READ_STATUS_ANY;
+            }
+
+            if (priority_high.isChecked()) {
+                mPriority = MessagesFilter.PRIORITY_HIGH;
+            } else if (priority_non_high.isChecked()) {
+                mPriority = MessagesFilter.PRIORITY_NON_HIGH;
+            } else {
+                mPriority = MessagesFilter.PRIORITY_ANY;
+            }
+
+            try {
+                mPeriodBegin = mSimpleDateFormat.parse(period_begin.getText().toString());
+            } catch (ParseException e) {
+                mPeriodBegin = null;
+                Logger.e(TAG, "Exception during parse begin period!");
+            }
+
+            try {
+                mPeriodEnd = mSimpleDateFormat.parse(period_end.getText().toString());
+            } catch (ParseException e) {
+                mPeriodEnd = null;
+                Logger.e(TAG, "Exception during parse end period!");
+            }
+
+            mRecipient = recipient.getText().toString();
+            mOriginator = originator.getText().toString();
+        }
+    }
+
+    public void onClickGetMessagesListing(View view) {
+        String folder = mSpinnerFolders.getSelectedItem().toString();
+        int maxListCount = Integer.parseInt(
+                mEditTextMaxListCountMessages.getText().toString());
+        int listStartOffset = Integer.parseInt(
+                mEditTextListStartOffsetMessages.getText().toString());
+        int subjectLength = Integer.parseInt(
+                mEditTextSubjectLength.getText().toString());
+
+        if (folder.equals(".")) {
+            folder = "";
+        }
+
+        try {
+            mProfileService.getMapClient(mMasInstanceId).getMessagesListing(folder,
+                    mMessageListingParameters,
+                    getLocalMessageFilter(), subjectLength, maxListCount, listStartOffset);
+
+            goToState(Job.GET_MESSAGE_LISTING);
+            updateListEmptyView(true);
+        } catch (IllegalArgumentException e) {
+            Toast.makeText(this,
+                    "GetMessagesListing FAILED: illegal arguments (" + e.getMessage() + ")",
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    public void updateListEmptyView(boolean working) {
+        View progressBar = findViewById(R.id.maptest_msglist_progressbar);
+        View textView = findViewById(R.id.maptest_msglist_empty);
+
+        if (working) {
+            textView.setVisibility(View.GONE);
+            mListViewMessages.setEmptyView(progressBar);
+        } else {
+            progressBar.setVisibility(View.GONE);
+            mListViewMessages.setEmptyView(textView);
+        }
+    }
+
+    public void onClickGetMessageListingSize(View view) {
+        mProfileService.getMapClient(mMasInstanceId).getMessagesListingSize();
+        goToState(Job.GET_MESSAGE_LISTING_SIZE);
+    }
+
+    class BluetoothMapMessageAdapter extends ArrayAdapter<BluetoothMapMessage> {
+        BluetoothMapMessageAdapter() {
+            super(MapTestActivity.this, android.R.layout.simple_list_item_1, mModelMessages);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View v = convertView;
+
+            if (v == null) {
+                v = getLayoutInflater().inflate(R.layout.message_row, parent, false);
+            }
+
+            BluetoothMapMessage msg = mModelMessages.get(position);
+
+            ((TextView) v.findViewById(R.id.message_row_type)).setText(msg.getType().toString());
+            ((TextView) v.findViewById(R.id.message_row_handle)).setText(msg.getHandle());
+
+            ((TextView) v.findViewById(R.id.message_row_flag_text))
+                    .setTextColor(msg.isText() ? Color.YELLOW : Color.DKGRAY);
+            ((TextView) v.findViewById(R.id.message_row_flag_read))
+                    .setTextColor(msg.isRead() ? Color.YELLOW : Color.DKGRAY);
+            ((TextView) v.findViewById(R.id.message_row_flag_sent))
+                    .setTextColor(msg.isSent() ? Color.YELLOW : Color.DKGRAY);
+            ((TextView) v.findViewById(R.id.message_row_flag_drm))
+                    .setTextColor(msg.isProtected() ? Color.YELLOW : Color.DKGRAY);
+            ((TextView) v.findViewById(R.id.message_row_flag_prio))
+                    .setTextColor(msg.isPriority() ? Color.YELLOW : Color.DKGRAY);
+
+            ((TextView) v.findViewById(R.id.message_row_subject)).setText(msg.getSubject());
+
+            ((TextView) v.findViewById(R.id.message_row_date))
+                    .setText(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(msg.getDateTime()));
+
+            ((TextView) v.findViewById(R.id.message_row_from)).setText(msg.getSenderAddressing());
+            if (msg.getSenderAddressing() != null && !msg.getSenderAddressing().isEmpty()) {
+                ((TextView) v.findViewById(R.id.message_row_from_lbl))
+                        .setVisibility(View.VISIBLE);
+            } else {
+                ((TextView) v.findViewById(R.id.message_row_from_lbl))
+                        .setVisibility(View.INVISIBLE);
+            }
+
+            ((TextView) v.findViewById(R.id.message_row_to)).setText(msg.getRecipientAddressing());
+            if (msg.getRecipientAddressing() != null && !msg.getRecipientAddressing().isEmpty()) {
+                ((TextView) v.findViewById(R.id.message_row_to_lbl)).setVisibility(View.VISIBLE);
+            } else {
+                ((TextView) v.findViewById(R.id.message_row_to_lbl)).setVisibility(View.INVISIBLE);
+            }
+
+            return v;
+        }
+    }
+
+    class DateTimePicker {
+        View view = null;
+        EditText editText = null;
+        DatePicker dataPicker = null;
+        TimePicker timePicker = null;
+
+        DateTimePicker(EditText ref) {
+            editText = ref;
+
+            view = getLayoutInflater().inflate(R.layout.date_time_picker, null);
+
+            dataPicker = (DatePicker) view.findViewById(R.id.date_picker);
+            timePicker = (TimePicker) view.findViewById(R.id.time_picker);
+            timePicker.setIs24HourView(true);
+
+            String oldValue = editText.getText().toString();
+
+            if (oldValue != null && !oldValue.isEmpty() && !oldValue.equals("")) {
+                try {
+                    Calendar cal = Calendar.getInstance();
+                    cal.setTime(mSimpleDateFormat.parse(oldValue));
+                    dataPicker.updateDate(cal.get(Calendar.YEAR),
+                            cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH));
+                    timePicker.setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
+                    timePicker.setCurrentMinute(cal.get(Calendar.MINUTE));
+                } catch (ParseException e) {
+                    Logger.e(TAG, "Parse exception in DataTimePicker!");
+                }
+            }
+        }
+
+        public void pick() {
+            AlertDialog.Builder alertBuilder = new AlertDialog.Builder(MapTestActivity.this);
+            alertBuilder.setView(view);
+            alertBuilder.setTitle(getResources().getString(R.string.dialog_title_pick_date_time));
+            alertBuilder.setPositiveButton(android.R.string.ok,
+                    new DialogInterface.OnClickListener() {
+
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            int year = dataPicker.getYear();
+                            int month = dataPicker.getMonth();
+                            int day = dataPicker.getDayOfMonth();
+                            int hour = timePicker.getCurrentHour();
+                            int minute = timePicker.getCurrentMinute();
+
+                            editText.setText(mSimpleDateFormat.format(new GregorianCalendar(
+                                    year, month, day, hour, minute, 0).getTime()));
+                        }
+                    });
+            alertBuilder.setNegativeButton(android.R.string.cancel, null);
+            alertBuilder.show();
+        }
+    }
+
+    private void resetPreviewEditUi() {
+        mViewBmsgHandle.setText("");
+
+        mViewBmsgStatus.setText("");
+        mViewBmsgType.setText("");
+        mViewBmsgFolder.setText("");
+
+        mViewBmsgEncoding.setText("");
+        mViewBmsgCharset.setText("");
+        mViewBmsgLanguage.setText("");
+
+        mViewBmsgContents.setText("");
+
+        mViewBmsgOrig.setText("");
+        mViewBmsgRcpt.setText("");
+
+        mPreviewMsgHandle = null;
+        mMapBmessage = null;
+
+        invalidateOptionsMenu();
+    }
+
+    private void updateUi(boolean invalidateOptionsMenu) {
+        if (mListTouchables == null) {
+            LinearLayout lay = (LinearLayout) findViewById(R.id.maptest_tab_list);
+            mListTouchables = lay.getTouchables();
+        }
+
+        for (View view : mListTouchables) {
+            view.setEnabled(mProfileService.getMapClient(mMasInstanceId).getState() == BluetoothMasClient.ConnectionState.CONNECTED
+                    && mCurrentJob == Job.IDLE);
+        }
+
+        if (invalidateOptionsMenu) {
+            invalidateOptionsMenu();
+        }
+    }
+
+    private void goToState(Job job) {
+        Logger.v(TAG, "Switch to " + job + " state from " + mCurrentJob.toString() + ".");
+        mCurrentJob = job;
+        updateUi(true);
+    }
+
+    private void updateMessage(BluetoothMapBmessage message) {
+        StringBuilder sb;
+
+        mPreviewMsgHandle = mPendingGetMessageHandle;
+        mPendingGetMessageHandle = null;
+
+        mMapBmessage = message;
+
+        boolean isRead = mMapBmessage.getStatus().equals(BluetoothMapBmessage.Status.READ);
+        ((Button) (findViewById(R.id.map_msg_set_status))).setText(
+                isRead ? getString(R.string.map_set_unread) : getString(R.string.map_set_read));
+
+        mActionBar.selectTab(mActionBar.getTabAt(1));
+
+        mViewBmsgHandle.setText(mPreviewMsgHandle);
+
+        mViewBmsgStatus.setText(message.getStatus().toString());
+        mViewBmsgType.setText(message.getType().toString());
+        mViewBmsgFolder.setText(message.getFolder());
+
+        mViewBmsgEncoding.setText(message.getEncoding());
+        mViewBmsgCharset.setText(message.getCharset());
+        mViewBmsgLanguage.setText(message.getLanguage());
+
+        mViewBmsgContents.setText(message.getBodyContent());
+
+        sb = new StringBuilder();
+        for (VCardEntry vcard : message.getOriginators()) {
+            sb.append(vcard.getDisplayName());
+            if (message.getType().equals(BluetoothMapBmessage.Type.EMAIL)) {
+                if (vcard.getEmailList() != null && vcard.getEmailList().size() > 0) {
+                    sb.append(" <").append(vcard.getEmailList().get(0).getAddress()).append(">");
+                }
+            } else {
+                if (vcard.getPhoneList() != null && vcard.getPhoneList().size() > 0) {
+                    sb.append(" <").append(vcard.getPhoneList().get(0).getNumber()).append(">");
+                }
+            }
+            sb.append(", ");
+        }
+        mViewBmsgOrig.setText(sb.toString());
+
+        sb = new StringBuilder();
+        for (VCardEntry vcard : message.getRecipients()) {
+            sb.append(vcard.getDisplayName());
+            if (message.getType().equals(BluetoothMapBmessage.Type.EMAIL)) {
+                if (vcard.getEmailList() != null && vcard.getEmailList().size() > 0) {
+                    sb.append(" <").append(vcard.getEmailList().get(0).getAddress()).append(">");
+                }
+            } else {
+                if (vcard.getPhoneList() != null && vcard.getPhoneList().size() > 0) {
+                    sb.append(" <").append(vcard.getPhoneList().get(0).getNumber()).append(">");
+                }
+            }
+            sb.append(", ");
+        }
+        mViewBmsgRcpt.setText(sb.toString());
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, v, menuInfo);
+
+        menu.add(0, 1, 0, "status -> READ");
+        menu.add(0, 2, 0, "status -> UNREAD");
+        menu.add(0, 3, 0, "status -> DELETED");
+        menu.add(0, 4, 0, "status -> UNDELETED");
+        menu.add(0, 5, 0, "get in native charset");
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+
+        BluetoothMapMessage bmsg = mAdapterMessages.getItem(info.position);
+
+        switch (item.getItemId()) {
+            case 1:
+            case 2:
+                mProfileService.getMapClient(mMasInstanceId).setMessageReadStatus(bmsg.getHandle(),
+                        item.getItemId() == 1);
+                goToState(Job.SET_STATUS_READ);
+                break;
+            case 3:
+            case 4:
+                mProfileService.getMapClient(mMasInstanceId).setMessageDeletedStatus(
+                        bmsg.getHandle(), item.getItemId() == 3);
+                goToState(Job.DELETE_MESSAGE);
+                break;
+            case 5:
+                getMessage(bmsg.getHandle(), CharsetType.NATIVE, false);
+                break;
+        }
+
+        return true;
+    }
+
+    public void onClickRemovePeople(View v) {
+        if (v.getId() == R.id.bmsgedit_orig_del) {
+            new StringListDialogFragment(mEditOriginators).show(getFragmentManager(), "orig_del");
+        } else if (v.getId() == R.id.bmsgedit_rcpt_del) {
+            new StringListDialogFragment(mEditRecipients).show(getFragmentManager(), "rcpt_del");
+        }
+    }
+
+    public void onClickAddPerson(View v) {
+        if (v.getId() == R.id.bmsgedit_rcpt_add) {
+            new GetTextDialogFragment().show(getFragmentManager(), "rcpt_add");
+        } else if (v.getId() == R.id.bmsgedit_orig_add) {
+            new GetTextDialogFragment().show(getFragmentManager(), "orig_add");
+        }
+    }
+
+    @Override
+    public void onGetTextDialogPositive(DialogFragment dialog, String text) {
+        if ("rcpt_add".equals(dialog.getTag())) {
+            mEditRecipients.add(text);
+        } else if ("orig_add".equals(dialog.getTag())) {
+            mEditOriginators.add(text);
+        }
+
+        refreshEditPeople();
+    }
+
+    @Override
+    public void onStringListDialogPositive(DialogFragment dialog, ArrayList<String> elements) {
+        if ("rcpt_del".equals(dialog.getTag())) {
+            mEditRecipients.clear();
+            mEditRecipients.addAll(elements);
+        } else if ("orig_del".equals(dialog.getTag())) {
+            mEditOriginators.clear();
+            mEditOriginators.addAll(elements);
+        }
+
+        refreshEditPeople();
+    }
+
+    private void refreshEditPeople() {
+        StringBuilder sb;
+
+        sb = new StringBuilder();
+        for (String s : mEditOriginators) {
+            sb.append(s).append(", ");
+        }
+        ((TextView) findViewById(R.id.bmsgedit_orig)).setText(sb.toString());
+
+        sb = new StringBuilder();
+        for (String s : mEditRecipients) {
+            sb.append(s).append(", ");
+        }
+        ((TextView) findViewById(R.id.bmsgedit_rcpt)).setText(sb.toString());
+    }
+
+    public void onClickGetMessage(View v) {
+        String handle = mViewBmsgHandle.getText().toString();
+        int selCharset = ((RadioGroup) findViewById(R.id.map_msg_get_charset))
+                .getCheckedRadioButtonId();
+        boolean attachment = ((CheckBox) findViewById(R.id.map_msg_get_attachment)).isChecked();
+
+        CharsetType charset = (selCharset == R.id.map_msg_get_charset_native ? CharsetType.NATIVE
+                : CharsetType.UTF_8);
+
+        if (!getMessage(handle, charset, attachment)) {
+            Toast.makeText(MapTestActivity.this, "GetMessage rejected, check parameters",
+                    Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    public void onClickCopyMessage(View v) {
+        String type = mViewBmsgType.getText().toString();
+        Spinner typeView = (Spinner) findViewById(R.id.bmsgedit_type);
+
+        for (int i = 0; i < typeView.getCount(); i++) {
+            if (type.equals(typeView.getItemAtPosition(i))) {
+                typeView.setSelection(i);
+                break;
+            }
+        }
+
+        String enc = mViewBmsgEncoding.getText().toString();
+        Spinner encView = (Spinner) findViewById(R.id.bmsgedit_encoding);
+
+        for (int i = 0; i < encView.getCount(); i++) {
+            if (enc.equals(encView.getItemAtPosition(i))) {
+                encView.setSelection(i);
+                break;
+            }
+        }
+
+        ((EditText) findViewById(R.id.bmsgedit_contents)).setText(mViewBmsgContents.getText());
+    }
+
+    private void goToFolder(String dst) {
+        mSetPathQueue = new ArrayDeque<String>(Arrays.asList(dst.split("/")));
+
+        mProfileService.getMapClient(mMasInstanceId).setFolderRoot();
+    }
+
+    private boolean getMessage(String handle, CharsetType charset, boolean attachments) {
+        if (mProfileService == null || handle == null) {
+            return false;
+        }
+
+        BluetoothMasClient cli = mProfileService.getMapClient(mMasInstanceId);
+
+        if (!cli.getMessage(handle, charset, attachments)) {
+            return false;
+        }
+
+        goToState(Job.GET_MESSAGE);
+
+        mPendingGetMessageHandle = handle;
+
+        return true;
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MessageFilterActivity.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MessageFilterActivity.java
new file mode 100644
index 0000000..2bfb977
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MessageFilterActivity.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.bttestapp;
+
+import android.os.Bundle;
+
+import org.codeaurora.bluetooth.bttestapp.R;
+import org.codeaurora.bluetooth.bttestapp.util.Logger;
+
+public class MessageFilterActivity extends FilterActivity {
+
+    private final String TAG = "MessageFilterActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Logger.v(TAG, "onCreate()");
+        ActivityHelper.initialize(this, R.layout.activity_message_filter);
+        ActivityHelper.setActionBarTitle(this, R.string.title_message_filter);
+    }
+
+    @Override
+    protected void fillParameters() {
+        addParameter(0, R.id.param_subject);
+        addParameter(1, R.id.param_datatime);
+        addParameter(2, R.id.param_sender_name);
+        addParameter(3, R.id.param_sender_addressing);
+        addParameter(4, R.id.param_receipent_name);
+        addParameter(5, R.id.param_receipent_addressing);
+        addParameter(6, R.id.param_type);
+        addParameter(7, R.id.param_size);
+        addParameter(8, R.id.param_reception_status);
+        addParameter(9, R.id.param_text);
+        addParameter(10, R.id.param_attachment_size);
+        addParameter(11, R.id.param_priority);
+        addParameter(12, R.id.param_read);
+        addParameter(13, R.id.param_sent);
+        addParameter(14, R.id.param_protected);
+        addParameter(15, R.id.param_replayto_addressing);
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MonkeyActivity.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MonkeyActivity.java
new file mode 100644
index 0000000..e8839c1
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/MonkeyActivity.java
@@ -0,0 +1,351 @@
+/*
+ * 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.bttestapp;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.TextView;
+
+public class MonkeyActivity extends Activity {
+
+    private static final String TAG = "MonkeyActivity";
+
+    public static final String ACTION_MONKEY = "com.qualcomm.bluetooth.action.MONKEY";
+
+    public static final String ACTION_MONKEY_QUERY = "com.qualcomm.bluetooth.action.MONKEY_QUERY";
+
+    public static final String EXTRA_OP = "extra.op";
+    public static final String EXTRA_DIALOG_TAG = "extra.dialogTag";
+    public static final String EXTRA_ID = "extra.id";
+    public static final String EXTRA_TEXT = "extra.text";
+
+    public static final String OP_DUMP = "dump";
+    public static final String OP_SELECT_TAB = "selectTab";
+    public static final String OP_CLICK = "click";
+    public static final String OP_LONG_CLICK = "longClick";
+    public static final String OP_SET_TEXT = "setText";
+    public static final String OP_SELECT_BY_TEXT = "selectByText";
+    public static final String OP_CLICK_BUTTON = "clickButton";
+    public static final String OP_CLICK_ACTION_BAR_MENU_BY_TEXT = "clickActionBarMenuByText";
+
+    protected Menu mActionBarMenu = null;
+
+    BroadcastReceiver mReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.d(TAG, "received " + action);
+
+            if (ACTION_MONKEY.equals(action)) {
+                String op = intent.getStringExtra(EXTRA_OP);
+                String dialogTag = intent.getStringExtra(EXTRA_DIALOG_TAG);
+                String id = intent.getStringExtra(EXTRA_ID);
+                String text = intent.getStringExtra(EXTRA_TEXT);
+
+                if (OP_DUMP.equals(op)) {
+                    getViewById(dialogTag, null);
+                } else if (OP_SELECT_TAB.equals(op)) {
+                    monkeySelectTab(text);
+                } else if (OP_CLICK.equals(op)) {
+                    monkeyClick(dialogTag, id, false);
+                } else if (OP_LONG_CLICK.equals(op)) {
+                    monkeyClick(dialogTag, id, true);
+                } else if (OP_SET_TEXT.equals(op)) {
+                    monkeySetText(dialogTag, id, text);
+                } else if (OP_SELECT_BY_TEXT.equals(op)) {
+                    monkeySelectByText(dialogTag, id, text);
+                } else if (OP_CLICK_BUTTON.equals(op)) {
+                    monkeyClickButton(dialogTag, id);
+                } else if (OP_CLICK_ACTION_BAR_MENU_BY_TEXT.equals(op)) {
+                    monkeyClickActionBarMenuByText(text);
+                }
+            } else if (ACTION_MONKEY_QUERY.equals(action)) {
+                String op = intent.getStringExtra(EXTRA_OP);
+
+                if (op != null) {
+                    onMonkeyQuery(op, intent.getExtras());
+                }
+            }
+        }
+    };
+
+    @Override
+    protected void onResume() {
+        Log.v(TAG, "onResume");
+        super.onResume();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_MONKEY);
+        filter.addAction(ACTION_MONKEY_QUERY);
+        registerReceiver(mReceiver, filter);
+    }
+
+    @Override
+    protected void onPause() {
+        Log.v(TAG, "onPause");
+        super.onPause();
+
+        unregisterReceiver(mReceiver);
+    }
+
+    private View getViewById(View view, String id, String tag, int nest) {
+        if (view == null) {
+            return null;
+        }
+
+        String tid = null;
+
+        final int vid = view.getId();
+        final Resources res = view.getResources();
+        if (vid != View.NO_ID && vid != 0 && res != null) {
+            if ((vid & 0xff000000) == 0x7f000000) {
+                tid = res.getResourceEntryName(vid);
+
+                if (id != null) {
+                    boolean match = false;
+                    if (id.equals(tid)) {
+                        if (tag == null) {
+                            match = true;
+                        } else {
+                            Object ttag = view.getTag();
+                            match = (ttag != null && ttag.toString().equals(tag));
+                        }
+                    }
+
+                    if (match) {
+                        return view;
+                    }
+                }
+            }
+        }
+
+        if (id == null) {
+            StringBuilder sb = new StringBuilder();
+
+            for (int i = 0; i < nest; i++) {
+                sb.append("  ");
+            }
+
+            sb.append(view.getClass().getSimpleName());
+
+            if (view instanceof TextView) {
+                String txt = ((TextView) view).getText().toString();
+
+                if (txt.length() > 0) {
+                    sb.append(" \"").append(txt).append("\"");
+                }
+            }
+
+            if (tid != null) {
+                sb.append(" [").append(tid);
+                if (view.getTag() != null) {
+                    sb.append("#").append(view.getTag().toString());
+                }
+                sb.append("]");
+            }
+
+            Log.v("DUMP", sb.toString());
+        }
+
+        View ret = null;
+
+        if (view instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) view;
+
+            int count = vg.getChildCount();
+            for (int idx = 0; idx < count && ret == null; idx++) {
+                ret = getViewById(vg.getChildAt(idx), id, tag, nest + 1);
+            }
+        }
+
+        return ret;
+    }
+
+    private View getViewById(String dialogTag, String id) {
+        View root;
+        String tag = null;
+
+        if (dialogTag != null) {
+            Fragment frag = getFragmentManager().findFragmentByTag(dialogTag);
+
+            if (!(frag instanceof DialogFragment)) {
+                return null;
+            }
+
+            root = ((DialogFragment) frag).getDialog().findViewById(android.R.id.content);
+        } else {
+            root = findViewById(android.R.id.content);
+        }
+
+        if (id != null) {
+            int hashPos = id.lastIndexOf("#");
+            if (hashPos > 0) {
+                tag = id.substring(hashPos + 1);
+                id = id.substring(0, hashPos);
+            }
+        }
+
+        return getViewById(root, id, tag, 0);
+    }
+
+    private void monkeySelectTab(String text) {
+        ActionBar ab = getActionBar();
+
+        if (ab == null) {
+            Log.w(TAG, "selectTab not found");
+            return;
+        }
+
+        int count = ab.getTabCount();
+        for (int idx = 0; idx < count; idx++) {
+            ActionBar.Tab tab = ab.getTabAt(idx);
+            if (tab.getText().toString().toLowerCase().equals(text.toLowerCase())) {
+                ab.selectTab(tab);
+                return;
+            }
+        }
+    }
+
+    private void monkeyClick(String dialogTag, String id, boolean longClick) {
+        View view = getViewById(dialogTag, id);
+
+        if (view == null) {
+            Log.w(TAG, "click(" + dialogTag + ", " + id + ") not found");
+            return;
+        }
+
+        if (longClick) {
+            view.performLongClick();
+        } else {
+            view.performClick();
+        }
+    }
+
+    private void monkeySetText(String dialogTag, String id, String text) {
+        View view = getViewById(dialogTag, id);
+
+        if (view == null || !(view instanceof TextView)) {
+            Log.w(TAG, "setText(" + dialogTag + ", " + id + ") not found");
+            return;
+        }
+
+        TextView txt = (TextView) view;
+        txt.setText(text);
+    }
+
+    private void monkeySelectByText(String dialogTag, String id, String text) {
+        View view = getViewById(dialogTag, id);
+
+        if (view == null || !(view instanceof AdapterView)) {
+            Log.w(TAG, "selectByText(" + dialogTag + ", " + id + ") not found");
+            return;
+        }
+
+        @SuppressWarnings("unchecked")
+        AdapterView<Adapter> adv = (AdapterView<Adapter>) view;
+
+        int count = adv.getCount();
+        for (int idx = 0; idx < count; idx++) {
+            if (adv.getItemAtPosition(idx).toString().equals(text)) {
+                adv.setSelection(idx);
+                return;
+            }
+        }
+    }
+
+    private void monkeyClickButton(String dialogTag, String id) {
+        if (dialogTag == null) {
+            return;
+        }
+
+        int which;
+
+        try {
+            which = Integer.parseInt(id);
+        } catch (NumberFormatException e) {
+            return;
+        }
+
+        Fragment frag = getFragmentManager().findFragmentByTag(dialogTag);
+
+        if (!(frag instanceof DialogFragment)) {
+            return;
+        }
+
+        Dialog dlg = ((DialogFragment) frag).getDialog();
+
+        if (!(dlg instanceof AlertDialog)) {
+            return;
+        }
+
+        Button btn = ((AlertDialog) dlg).getButton(which);
+
+        btn.performClick();
+    }
+
+    private void monkeyClickActionBarMenuByText(String text) {
+        if (mActionBarMenu == null || text == null) {
+            return;
+        }
+
+        for (int idx = 0; idx < mActionBarMenu.size(); idx++) {
+            MenuItem item = mActionBarMenu.getItem(idx);
+
+            String title = item.getTitle().toString();
+
+            if (title.toLowerCase().equals(text.toLowerCase())) {
+                mActionBarMenu.performIdentifierAction(item.getItemId(), 0);
+                break;
+            }
+        }
+    }
+
+    protected void onMonkeyQuery(String op, Bundle params) {
+        // do nothing
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/PbapTestActivity.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/PbapTestActivity.java
new file mode 100644
index 0000000..4815411
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/PbapTestActivity.java
@@ -0,0 +1,978 @@
+/*
+ * 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.bttestapp;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.ActionBar.TabListener;
+import android.app.FragmentTransaction;
+import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.RadioButton;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ViewFlipper;
+
+import com.android.vcard.VCardEntry;
+import org.codeaurora.bluetooth.pbapclient.BluetoothPbapCard;
+import org.codeaurora.bluetooth.pbapclient.BluetoothPbapClient;
+import org.codeaurora.bluetooth.bttestapp.R;
+import org.codeaurora.bluetooth.bttestapp.services.IPbapServiceCallback;
+import org.codeaurora.bluetooth.bttestapp.util.Logger;
+import org.codeaurora.bluetooth.bttestapp.util.MonkeyEvent;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class PbapTestActivity extends MonkeyActivity implements IBluetoothConnectionObserver {
+
+    private final String TAG = "PbapTestActivity";
+
+    /*
+     * Class constants.
+     */
+    private static final int REQUEST_CODE_GET_FILTER_FOR_DOWNLOAD_TAB = 0;
+    private static final int REQUEST_CODE_GET_FILTER_FOR_VCARD_TAB = 1;
+
+    private ArrayDeque<String> mSetPathQueue = null;
+
+    private static final short MAX_COUNT_DEFAULT_VALUE = 0;
+    private static final short OFFSET_DEFAULT_VALUE = 0;
+    private static final String HANDLE_DEFAULT_VALUE = "0.vcf";
+
+    /*
+     * Common UI.
+     */
+    private ActionBar mActionBar = null;
+    private ViewFlipper mViewFlipper = null;
+    /*
+     * Download functionality UI.
+     */
+    private Spinner mDownloadSpinner = null;
+    private RadioButton mRadioButtonVCard21 = null;
+    private RadioButton mRadioButtonVCard30 = null;
+    private EditText mEditTextDownloadMaxListCount = null;
+    private EditText mEditTextDownloadOffsetValue = null;
+    private Button mButtonFilter = null;
+    private Button mButtonDownloadSearch = null;
+
+    private ProgressBar mDownloadProgressBar = null;
+    private TextView mTextViewDownloadNothingFound = null;
+    private ListView mListViewDownloadContacts = null;
+
+    private RadioButton mRadioButtonSearchName = null;
+    private RadioButton mRadioButtonSearchNumber = null;
+    private RadioButton mRadioButtonSearchSound = null;
+    private EditText mEditTextSearchValue = null;
+    private EditText mEditTextBrowseMaxListCount = null;
+    private EditText mEditTextBrowseOffsetValue = null;
+    private RadioButton mRadioButtonOrderUnordered = null;
+    private RadioButton mRadioButtonOrderAlphabetical = null;
+    private RadioButton mRadioButtonOrderIndexed = null;
+    private RadioButton mRadioButtonOrderPhonetic = null;
+    private Button mButtonBrowseSearch = null;
+
+    private ProgressBar mBrowseProgressBar = null;
+    private TextView mTextViewBrowseNothingFound = null;
+    private ListView mListViewBrowseContacts = null;
+
+    /*
+     * vCard Details functionality.
+     */
+    private Spinner mVcardSpinner = null;
+    private VcardView mVcardView = null;
+    private EditText mEditTextHandleValue = null;
+
+    /*
+     * Download local variables.
+     */
+    private long mDownloadValueFilter = 0;
+    private byte mDownloadValueCardType = BluetoothPbapClient.VCARD_TYPE_21;
+    private int mDownloadValueMaxCount = MAX_COUNT_DEFAULT_VALUE;
+    private int mDownloadValueOffset = OFFSET_DEFAULT_VALUE;
+
+    /*
+     * Browse local variables.
+     */
+    private byte mBrowseValueOrder = BluetoothPbapClient.ORDER_BY_DEFAULT;
+    private byte mBrowseValueSearchAttr = BluetoothPbapClient.SEARCH_ATTR_NAME;
+    private int mBrowseValueMaxCount = MAX_COUNT_DEFAULT_VALUE;
+    private int mBrowseValueOffset = OFFSET_DEFAULT_VALUE;
+
+    /*
+     * Action bar tabs.
+     */
+    private ActionBar.Tab mDownloadTab = null;
+    private ActionBar.Tab mBrowseTab = null;
+    private ActionBar.Tab mVcardTab = null;
+
+    /*
+     * vCard local variables.
+     */
+    private String mVcardHandleValue = HANDLE_DEFAULT_VALUE;
+    private long mVcardValueFilter = 0;
+    private byte mVcardValueCardType = BluetoothPbapClient.VCARD_TYPE_21;
+
+    /*
+     * Download adapter.
+     */
+    private VCardEntryAdapter mVCardEntryAdapter = null;
+
+    OnItemClickListener mOnDownloadItemClickListener = new OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            VCardEntry vcard = (VCardEntry) parent.getAdapter().getItem(position);
+            mVcardView.setVCardEntry(vcard);
+            mActionBar.selectTab(mVcardTab);
+        }
+    };
+
+    /*
+     * Browse adapter.
+     */
+    private BluetoothPbapCardAdapter mBluetoothPbapCardAdapter = null;
+
+    OnItemClickListener mOnBrowseItemClickListener = new OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            BluetoothPbapCard pbacpCard = (BluetoothPbapCard) parent.getAdapter().getItem(position);
+            mProfileService.getPbapClient().pullVcardEntry(pbacpCard.handle);
+        }
+    };
+
+    /*
+     * PBAP Service.
+     */
+    private ProfileService mProfileService = null;
+    private final IPbapServiceCallback mPbapServiceCallback = new IPbapServiceCallback() {
+
+        @Override
+        public void onSetPhoneBookDone() {
+            if (mSetPathQueue != null && mSetPathQueue.size() > 0) {
+                String next = mSetPathQueue.removeFirst();
+
+                setPhonebookFolder(next);
+            } else {
+                new MonkeyEvent("pbap-setphonebook", true).send();
+            }
+        }
+
+        @Override
+        public void onPullPhoneBookDone(ArrayList<VCardEntry> list, int missedCalls) {
+            mVCardEntryAdapter.clear();
+            mVCardEntryAdapter.addAll(list);
+
+            stopProgressBarDownload();
+
+            /* send event for monkeyrunner */
+            MonkeyEvent evt = new MonkeyEvent("pbap-pullphonebook", true);
+            evt.addReplyParam("size", list.size());
+            for (VCardEntry card : list) {
+                evt.addExtReply(BluetoothPbapCard.jsonifyVcardEntry(card));
+            }
+            evt.send();
+
+            Toast.makeText(PbapTestActivity.this, "Missed calls=" + missedCalls, Toast.LENGTH_SHORT)
+                    .show();
+        }
+
+        @Override
+        public void onPullVcardListingDone(ArrayList<BluetoothPbapCard> list, int missedCalls) {
+            mBluetoothPbapCardAdapter.clear();
+            mBluetoothPbapCardAdapter.addAll(list);
+            stopProgressBarBrowse();
+
+            /* send event for monkeyrunner */
+            new MonkeyEvent("pbap-pullvcardlisting", true)
+                    .addReplyParam("size", list.size())
+                    .addExtReply(list)
+                    .send();
+
+            Toast.makeText(PbapTestActivity.this, "Missed calls=" + missedCalls, Toast.LENGTH_SHORT)
+                    .show();
+        }
+
+        @Override
+        public void onPullVcardEntryDone(VCardEntry vcard) {
+            Logger.v(TAG, "onReceivedPbapPullVcardEntryDone()");
+
+            /* send event for monkeyrunner */
+            new MonkeyEvent("pbap-pullvcardentry", true)
+                    .addExtReply(BluetoothPbapCard.jsonifyVcardEntry(vcard))
+                    .send();
+
+            mVcardView.setVCardEntry(vcard);
+            mActionBar.selectTab(mVcardTab);
+        }
+
+        @Override
+        public void onPullPhoneBookSizeDone(int size, int type) {
+            /* send event for monkeyrunner */
+            if (type == 0) {
+                new MonkeyEvent("pbap-pullphonebook-size", true)
+                        .addReplyParam("size", size)
+                        .send();
+            } else {
+                new MonkeyEvent("pbap-pullvcardlisting-size", true)
+                        .addReplyParam("size", size)
+                        .send();
+            }
+
+            Toast.makeText(PbapTestActivity.this, "size=" + size, Toast.LENGTH_SHORT).show();
+        }
+
+        @Override
+        public void onSetPhoneBookError() {
+            Logger.e(TAG, "Received from PBAP set phone book error.");
+
+            mSetPathQueue = null;
+
+            new MonkeyEvent("pbap-setphonebook", false).send();
+        }
+
+        @Override
+        public void onPullPhoneBookError() {
+            Logger.e(TAG, "Received from PBAP pull phone book error.");
+            stopProgressBarDownload();
+            Toast.makeText(PbapTestActivity.this, "PullPhoneBook FAILED", Toast.LENGTH_LONG).show();
+            new MonkeyEvent("pbap-pullphonebook", false).send();
+        }
+
+        @Override
+        public void onPullVcardListingError() {
+            Logger.e(TAG, "Received from PBAP listing error.");
+            stopProgressBarBrowse();
+            Toast.makeText(PbapTestActivity.this, "PullvCardListing FAILED", Toast.LENGTH_LONG)
+                    .show();
+            new MonkeyEvent("pbap-pullvcardlisting", false).send();
+        }
+
+        @Override
+        public void onPullVcardEntryError() {
+            Logger.e(TAG, "Received from PBAP vCard entry error.");
+            Toast.makeText(PbapTestActivity.this, "PullvCardEntry FAILED", Toast.LENGTH_LONG)
+                    .show();
+            new MonkeyEvent("pbap-pullvcardentry", false).send();
+        }
+
+        @Override
+        public void onPullPhoneBookSizeError() {
+            Logger.e(TAG, "Received from PBAP pull phone book size error.");
+            Toast.makeText(PbapTestActivity.this, "PullPhoneBook size FAILED", Toast.LENGTH_LONG)
+                    .show();
+            new MonkeyEvent("pbap-pullphonebook-size", false).send();
+        }
+
+        @Override
+        public void onPullVcardListingSizeError() {
+            Logger.e(TAG, "Received from PBAP pull vCard listing sizeerror.");
+            Toast.makeText(PbapTestActivity.this, "PullvCardListing size FAILED", Toast.LENGTH_LONG)
+                    .show();
+            new MonkeyEvent("pbap-pullvcardlisting-size", false).send();
+        }
+
+        @Override
+        public void onSessionConnected() {
+            Toast.makeText(PbapTestActivity.this, "PBAP session connected", Toast.LENGTH_SHORT)
+                    .show();
+            invalidateOptionsMenu();
+        }
+
+        @Override
+        public void onSessionDisconnected() {
+            Toast.makeText(PbapTestActivity.this, "PBAP session disconnected", Toast.LENGTH_SHORT)
+                    .show();
+            invalidateOptionsMenu();
+        }
+    };
+
+    /*
+     * PBAP Service Connection.
+     */
+    private final ServiceConnection mPbapServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Logger.v(TAG, "onServiceConnected()");
+            mProfileService = ((ProfileService.LocalBinder) service).getService();
+            mProfileService.setPbapCallback(mPbapServiceCallback);
+
+            ProfileService.PbapSessionData pbap = mProfileService.getPbapSessionData();
+
+            if (pbap.pullPhoneBook != null) {
+                mVCardEntryAdapter.clear();
+                mVCardEntryAdapter.addAll(pbap.pullPhoneBook);
+                stopProgressBarDownload();
+            }
+
+            if (pbap.pullVcardListing != null) {
+                mBluetoothPbapCardAdapter.clear();
+                mBluetoothPbapCardAdapter.addAll(pbap.pullVcardListing);
+                stopProgressBarBrowse();
+            }
+
+            if (pbap.pullVcardEntry != null) {
+                mVcardView.setVCardEntry(pbap.pullVcardEntry);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Logger.v(TAG, "onServiceDisconnected()");
+            mProfileService = null;
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Logger.v(TAG, "onCreate()");
+        ActivityHelper.initialize(this, R.layout.activity_pbap_test);
+        ActivityHelper.setActionBarTitle(this, R.string.title_pbap_test);
+
+        prepareActionBar();
+        prepareUserInterfaceDownload();
+        prepareUserInterfaceBrowse();
+        prepareUserInterfaceVcard();
+
+        mBluetoothPbapCardAdapter = new BluetoothPbapCardAdapter();
+        mListViewBrowseContacts.setAdapter(mBluetoothPbapCardAdapter);
+
+        mVCardEntryAdapter = new VCardEntryAdapter();
+        mListViewDownloadContacts.setAdapter(mVCardEntryAdapter);
+
+        BluetoothConnectionReceiver.registerObserver(this);
+
+        // bind to PBAP service
+        Intent intent = new Intent(this, ProfileService.class);
+        bindService(intent, mPbapServiceConnection, BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        Logger.v(TAG, "onStart()");
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        Logger.v(TAG, "onStop()");
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        Logger.v(TAG, "onDestroy()");
+
+        mProfileService.setPbapCallback(null);
+
+        unbindService(mPbapServiceConnection);
+        BluetoothConnectionReceiver.removeObserver(this);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Logger.v(TAG, "onResume()");
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        Logger.v(TAG, "onPause()");
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        mActionBarMenu = menu;
+
+        getMenuInflater().inflate(R.menu.menu_pbap_test, menu);
+
+        if (mProfileService.getPbapClient().getState() != BluetoothPbapClient.ConnectionState.DISCONNECTED) {
+            menu.findItem(R.id.menu_pbap_disconnect).setVisible(true);
+        } else {
+            menu.findItem(R.id.menu_pbap_connect).setVisible(true);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_pbap_connect:
+                mProfileService.getPbapClient().connect();
+                break;
+            case R.id.menu_pbap_disconnect:
+                mProfileService.getPbapClient().disconnect();
+                break;
+            default:
+                Logger.w(TAG, "Unknown item selected.");
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        Logger.v(TAG, "onActivityResult()");
+
+        if (resultCode != RESULT_OK) {
+            Logger.w(TAG, "Result code is not ok for req: " + requestCode + ".");
+            return;
+        }
+
+        if (requestCode == REQUEST_CODE_GET_FILTER_FOR_DOWNLOAD_TAB) {
+            long result = data.getLongExtra("result", -1);
+
+            if (result != -1) {
+                Logger.d(TAG, "Received download tab filter " + result + ".");
+                mDownloadValueFilter = result;
+            } else {
+                Logger.w(TAG, "Somethings go wrong here!");
+            }
+        }
+
+        if (requestCode == REQUEST_CODE_GET_FILTER_FOR_VCARD_TAB) {
+            long result = data.getLongExtra("result", -1);
+
+            if (result != -1) {
+                Logger.d(TAG, "Received vcard tab filter " + result + ".");
+                mVcardValueFilter = result;
+            } else {
+                Logger.w(TAG, "Somethings go wrong here!");
+            }
+        }
+    }
+
+    private void prepareUserInterfaceDownload() {
+        mDownloadSpinner = (Spinner) findViewById(R.id.pbap_download_spinner);
+        mButtonFilter = (Button) findViewById(R.id.pbap_download_filter_button);
+        mButtonFilter.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mButtonFilter.onClick()");
+
+                Intent intent = new Intent(PbapTestActivity.this, VcardFilterActivity.class);
+                intent.putExtra("filter", mDownloadValueFilter);
+                intent.putExtra("type", mDownloadValueCardType);
+                startActivityForResult(intent, REQUEST_CODE_GET_FILTER_FOR_DOWNLOAD_TAB);
+            }
+        });
+
+        mEditTextDownloadMaxListCount = (EditText) findViewById(R.id.pbap_download_max_list_count);
+        mEditTextDownloadMaxListCount.setText(String.valueOf(mDownloadValueMaxCount));
+
+        mEditTextDownloadOffsetValue = (EditText) findViewById(R.id.pbap_download_offset_value);
+        mEditTextDownloadOffsetValue.setText(String.valueOf(mDownloadValueOffset));
+
+        mRadioButtonVCard21 = (RadioButton) findViewById(R.id.pbap_download_vcard21);
+        mRadioButtonVCard21.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mRadioButonVCard21.onClick()");
+                mDownloadValueCardType = BluetoothPbapClient.VCARD_TYPE_21;
+            }
+        });
+
+        mRadioButtonVCard30 = (RadioButton) findViewById(R.id.pbap_download_vcard30);
+        mRadioButtonVCard30.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mRadioButonVCard30.onClick()");
+                mDownloadValueCardType = BluetoothPbapClient.VCARD_TYPE_30;
+            }
+        });
+
+        mButtonDownloadSearch = (Button) findViewById(R.id.pbap_download_search);
+        mButtonDownloadSearch.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mButtonDownloadSearch.onClick()");
+                runPbapTestDownload();
+            }
+        });
+
+        mDownloadProgressBar = (ProgressBar) findViewById(R.id.pbap_download_progress_bar);
+        mTextViewDownloadNothingFound = (TextView) findViewById(R.id.pbap_download_test_list_empty);
+        mListViewDownloadContacts = (ListView) findViewById(R.id.pbap_download_listview_contacts_list);
+        mListViewDownloadContacts.setOnItemClickListener(mOnDownloadItemClickListener);
+
+        mDownloadProgressBar.setVisibility(View.GONE);
+        mListViewDownloadContacts.setVisibility(View.GONE);
+    }
+
+    private void prepareUserInterfaceBrowse() {
+        mRadioButtonSearchName = (RadioButton) findViewById(R.id.pbap_browse_name);
+        mRadioButtonSearchName.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mRadioButtonSearchName.onClick()");
+                mBrowseValueSearchAttr = BluetoothPbapClient.SEARCH_ATTR_NAME;
+            }
+        });
+
+        mRadioButtonSearchNumber = (RadioButton) findViewById(R.id.pbap_browse_number);
+        mRadioButtonSearchNumber.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mRadioButtonSearchNumber.onClick()");
+                mBrowseValueSearchAttr = BluetoothPbapClient.SEARCH_ATTR_NUMBER;
+            }
+        });
+
+        mRadioButtonSearchSound = (RadioButton) findViewById(R.id.pbap_browse_sound);
+        mRadioButtonSearchSound.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mRadioButtonSearchSound.onClick()");
+                mBrowseValueSearchAttr = BluetoothPbapClient.SEARCH_ATTR_SOUND;
+            }
+        });
+
+        mEditTextSearchValue = (EditText) findViewById(R.id.pbap_browse_search_value);
+
+        mEditTextBrowseMaxListCount = (EditText) findViewById(R.id.pbap_browse_max_list_count);
+        mEditTextBrowseMaxListCount.setText(String.valueOf(mBrowseValueMaxCount));
+
+        mEditTextBrowseOffsetValue = (EditText) findViewById(R.id.pbap_browse_offset_value);
+        mEditTextBrowseOffsetValue.setText(String.valueOf(mBrowseValueOffset));
+
+        mRadioButtonOrderUnordered = (RadioButton) findViewById(R.id.pbap_browse_unordered);
+        mRadioButtonOrderUnordered.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mRadioButtonOrderUnordered.onClick()");
+                mBrowseValueOrder = BluetoothPbapClient.ORDER_BY_DEFAULT;
+            }
+        });
+
+        mRadioButtonOrderAlphabetical = (RadioButton) findViewById(R.id.pbap_browse_alphabetical);
+        mRadioButtonOrderAlphabetical.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mRadioButtonOrderAlphabetical.onClick()");
+                mBrowseValueOrder = BluetoothPbapClient.ORDER_BY_ALPHABETICAL;
+            }
+        });
+
+        mRadioButtonOrderIndexed = (RadioButton) findViewById(R.id.pbap_browse_indexed);
+        mRadioButtonOrderIndexed.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mRadioButtonOrderIndexed.onClick()");
+                mBrowseValueOrder = BluetoothPbapClient.ORDER_BY_INDEXED;
+            }
+        });
+
+        mRadioButtonOrderPhonetic = (RadioButton) findViewById(R.id.pbap_browse_phonetic);
+        mRadioButtonOrderPhonetic.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mRadioButtonOrderPhonetic.onClick()");
+                mBrowseValueOrder = BluetoothPbapClient.ORDER_BY_PHONETIC;
+            }
+        });
+
+        mButtonBrowseSearch = (Button) findViewById(R.id.pbap_browse_search);
+        mButtonBrowseSearch.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logger.v(TAG, "mButtonBrowseSearch.onClick()");
+                runPbapTestBrowse();
+            }
+        });
+
+        mBrowseProgressBar = (ProgressBar) findViewById(R.id.pbap_browse_progress_bar);
+        mTextViewBrowseNothingFound = (TextView) findViewById(R.id.pbap_browse_test_list_empty);
+        mListViewBrowseContacts = (ListView) findViewById(R.id.pbap_browse_listview_contacts_list);
+        mListViewBrowseContacts.setOnItemClickListener(mOnBrowseItemClickListener);
+
+        mBrowseProgressBar.setVisibility(View.GONE);
+        mListViewBrowseContacts.setVisibility(View.GONE);
+    }
+
+    private void prepareUserInterfaceVcard() {
+        mVcardSpinner = (Spinner) findViewById(R.id.pbap_vcard_spinner);
+        mVcardView = (VcardView) findViewById(R.id.pbap_vcard_vcardview);
+
+        mEditTextHandleValue = (EditText) findViewById(R.id.pbap_vcard_header);
+        mEditTextHandleValue.setText(mVcardHandleValue);
+    }
+
+    private void prepareActionBar() {
+        mActionBar = getActionBar();
+
+        if (mActionBar != null) {
+            mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+            mDownloadTab = mActionBar.newTab().setText(R.string.download);
+            mBrowseTab = mActionBar.newTab().setText(R.string.browse);
+            mVcardTab = mActionBar.newTab().setText(R.string.vcard);
+
+            mViewFlipper = (ViewFlipper) findViewById(R.id.pbap_test_viewflipper);
+
+            mDownloadTab.setTabListener(new TabListener() {
+                @Override
+                public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+                    /* NoP */
+                }
+
+                @Override
+                public void onTabSelected(Tab tab, FragmentTransaction ft) {
+                    mViewFlipper.setDisplayedChild(0);
+                }
+
+                @Override
+                public void onTabReselected(Tab tab, FragmentTransaction ft) {
+                    /* NoP */
+                }
+            });
+
+            mBrowseTab.setTabListener(new TabListener() {
+                @Override
+                public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+                    /* NoP */
+                }
+
+                @Override
+                public void onTabSelected(Tab tab, FragmentTransaction ft) {
+                    mViewFlipper.setDisplayedChild(1);
+                }
+
+                @Override
+                public void onTabReselected(Tab tab, FragmentTransaction ft) {
+                    /* NoP */
+                }
+            });
+
+            mVcardTab.setTabListener(new TabListener() {
+
+                @Override
+                public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+                    /* NoP */
+                }
+
+                @Override
+                public void onTabSelected(Tab tab, FragmentTransaction ft) {
+                    mViewFlipper.setDisplayedChild(2);
+                }
+
+                @Override
+                public void onTabReselected(Tab tab, FragmentTransaction ft) {
+                    /* NoP */
+                }
+            });
+
+            mActionBar.addTab(mDownloadTab);
+            mActionBar.addTab(mBrowseTab);
+            mActionBar.addTab(mVcardTab);
+        }
+    }
+
+    private void runPbapTestDownload() {
+        Logger.v(TAG, "runPbapTestDownload()");
+
+        try {
+            mDownloadValueMaxCount = Integer.parseInt(mEditTextDownloadMaxListCount.getText()
+                    .toString());
+        } catch (NumberFormatException e) {
+            mDownloadValueMaxCount = MAX_COUNT_DEFAULT_VALUE;
+        }
+
+        try {
+            mDownloadValueOffset = Integer.parseInt(mEditTextDownloadOffsetValue.getText()
+                    .toString());
+        } catch (NumberFormatException e) {
+            mDownloadValueOffset = OFFSET_DEFAULT_VALUE;
+        }
+
+        // Refresh UI EditTexts value if some are blank after edit.
+        mEditTextDownloadMaxListCount.setText(String.valueOf(mDownloadValueMaxCount));
+        mEditTextDownloadOffsetValue.setText(String.valueOf(mDownloadValueOffset));
+
+        try {
+            if (mProfileService.getPbapClient().pullPhoneBook(
+                    mDownloadSpinner.getSelectedItem().toString(), mDownloadValueFilter,
+                    mDownloadValueCardType, mDownloadValueMaxCount, mDownloadValueOffset)) {
+                startProgressBarDownload();
+            } else {
+                Toast.makeText(this, "PullPhoneBook FAILED", Toast.LENGTH_LONG).show();
+            }
+        } catch (IllegalArgumentException e) {
+            Toast.makeText(this,
+                    "PullPhoneBook FAILED: illegal arguments (" + e.getMessage() + ")",
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void startProgressBarDownload() {
+        mDownloadProgressBar.setVisibility(View.VISIBLE);
+        mListViewDownloadContacts.setVisibility(View.GONE);
+        mTextViewDownloadNothingFound.setVisibility(View.GONE);
+    }
+
+    private void stopProgressBarDownload() {
+        mDownloadProgressBar.setVisibility(View.GONE);
+        if (mVCardEntryAdapter.getCount() == 0) {
+            mTextViewDownloadNothingFound.setVisibility(View.VISIBLE);
+            mListViewDownloadContacts.setVisibility(View.GONE);
+        } else {
+            mTextViewDownloadNothingFound.setVisibility(View.GONE);
+            mListViewDownloadContacts.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void runPbapTestBrowse() {
+        Logger.v(TAG, "runPbapTestBrowse()");
+
+        byte order = mBrowseValueOrder;
+        String searchValue = mEditTextSearchValue.getText().toString();
+
+        try {
+            mBrowseValueMaxCount = Integer.parseInt(mEditTextBrowseMaxListCount.getText()
+                    .toString());
+        } catch (NumberFormatException e) {
+            mBrowseValueMaxCount = MAX_COUNT_DEFAULT_VALUE;
+        }
+
+        try {
+            mBrowseValueOffset = Integer.parseInt(mEditTextBrowseOffsetValue.getText().toString());
+        } catch (NumberFormatException e) {
+            mBrowseValueOffset = OFFSET_DEFAULT_VALUE;
+        }
+
+        // Refresh UI EditTexts value if some are blank after edit.
+        mEditTextBrowseMaxListCount.setText(String.valueOf(mBrowseValueMaxCount));
+        mEditTextBrowseOffsetValue.setText(String.valueOf(mBrowseValueOffset));
+
+        try {
+            boolean started;
+
+            if (searchValue != "" && !searchValue.isEmpty()) {
+                started = mProfileService.getPbapClient().pullVcardListing(null, order,
+                        mBrowseValueSearchAttr, searchValue, mBrowseValueMaxCount,
+                        mBrowseValueOffset);
+            } else {
+                started = mProfileService.getPbapClient().pullVcardListing(null, order,
+                        mBrowseValueMaxCount, mBrowseValueOffset);
+            }
+
+            if (started) {
+                startProgressBarBrowse();
+            } else {
+                Toast.makeText(this, "PullvCardListing FAILED", Toast.LENGTH_LONG).show();
+            }
+        } catch (IllegalArgumentException e) {
+            Toast.makeText(this,
+                    "PullvCardListing FAILED: illegal arguments (" + e.getMessage() + ")",
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void startProgressBarBrowse() {
+        mBrowseProgressBar.setVisibility(View.VISIBLE);
+        mListViewBrowseContacts.setVisibility(View.GONE);
+        mTextViewBrowseNothingFound.setVisibility(View.GONE);
+    }
+
+    private void stopProgressBarBrowse() {
+        mBrowseProgressBar.setVisibility(View.GONE);
+        if (mBluetoothPbapCardAdapter.getCount() == 0) {
+            mTextViewBrowseNothingFound.setVisibility(View.VISIBLE);
+            mListViewBrowseContacts.setVisibility(View.GONE);
+        } else {
+            mTextViewBrowseNothingFound.setVisibility(View.GONE);
+            mListViewBrowseContacts.setVisibility(View.VISIBLE);
+        }
+    }
+
+    class VCardEntryAdapter extends ArrayAdapter<VCardEntry> {
+        VCardEntryAdapter() {
+            super(PbapTestActivity.this, android.R.layout.simple_list_item_1);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View row = convertView;
+            boolean defaultPhoto = true;
+
+            if (row == null) {
+                LayoutInflater inflater = getLayoutInflater();
+                row = inflater.inflate(R.layout.vcard_row, null);
+            }
+
+            VCardEntry tmp = getItem(position);
+
+            ((TextView) row.findViewById(R.id.vcard_title)).setText(tmp.getDisplayName());
+
+            if (tmp.getPhotoList() != null && tmp.getPhotoList().size() > 0) {
+                byte[] data = tmp.getPhotoList().get(0).getBytes();
+                try {
+                    if (data != null) {
+                        Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
+                        ((ImageView) row.findViewById(R.id.vcard_photo)).setImageBitmap(bmp);
+                        defaultPhoto = false;
+                    }
+                } catch (IllegalArgumentException e) {
+                }
+            }
+
+            if (defaultPhoto) {
+                ((ImageView) row.findViewById(R.id.vcard_photo))
+                        .setImageResource(R.drawable.ic_contact_picture);
+            }
+
+            return row;
+        }
+    }
+
+    class BluetoothPbapCardAdapter extends ArrayAdapter<BluetoothPbapCard> {
+        BluetoothPbapCardAdapter() {
+            super(PbapTestActivity.this, android.R.layout.simple_list_item_1);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View row = convertView;
+
+            if (row == null) {
+                LayoutInflater inflater = getLayoutInflater();
+                row = inflater.inflate(R.layout.vcard_row, null);
+            }
+
+            BluetoothPbapCard tmp = getItem(position);
+
+            StringBuilder sb = new StringBuilder();
+
+            if (tmp.firstName != null) {
+                sb.append(tmp.firstName).append(" ");
+            }
+            sb.append(tmp.lastName);
+
+            ((TextView) row.findViewById(R.id.vcard_title)).setText(sb.toString());
+            return row;
+        }
+    }
+
+    @Override
+    public void onDeviceChanged(BluetoothDevice device) {
+        Logger.v(TAG, "onDeviceChanged()");
+        /* NoP */
+    }
+
+    @Override
+    public void onDeviceDisconected() {
+        Logger.e(TAG, "BT device disconnected!");
+    }
+
+    public void onClick_download_getsize(View v) {
+        mProfileService.getPbapClient().pullPhoneBookSize(
+                mDownloadSpinner.getSelectedItem().toString());
+    }
+
+    public void onClick_browse_getsize(View v) {
+        mProfileService.getPbapClient().pullVcardListingSize("");
+    }
+
+    public void onClickVcardFilterAttributes(View v) {
+        Intent intent = new Intent(PbapTestActivity.this, VcardFilterActivity.class);
+        intent.putExtra("filter", mVcardValueFilter);
+        intent.putExtra("type", mVcardValueCardType);
+        startActivityForResult(intent, REQUEST_CODE_GET_FILTER_FOR_VCARD_TAB);
+    }
+
+    public void onClickRadioButtonVcardType21(View v) {
+        mVcardValueCardType = BluetoothPbapClient.VCARD_TYPE_21;
+    }
+
+    public void onClickRadioButtonVcardType30(View v) {
+        mVcardValueCardType = BluetoothPbapClient.VCARD_TYPE_30;
+    }
+
+    public void onClick_setPhonebook(View v) {
+        int id = getResources().getIdentifier(v.getTag().toString(), "id", getPackageName());
+
+        Spinner spinner = (Spinner) findViewById(id);
+
+        if (spinner != null) {
+            setPhonebook(spinner.getSelectedItem().toString());
+        }
+    }
+
+    public void onClickVcardButtonGet(View v) {
+        mVcardHandleValue = mEditTextHandleValue.getText().toString();
+
+        if (mVcardHandleValue.isEmpty() || mVcardHandleValue == null) {
+            mVcardHandleValue = HANDLE_DEFAULT_VALUE;
+            mEditTextHandleValue.setText(mVcardHandleValue);
+        }
+
+        mProfileService.getPbapClient().pullVcardEntry(mVcardHandleValue, mVcardValueFilter,
+                mVcardValueCardType);
+    }
+
+    private void setPhonebook(String dst) {
+        dst = dst.replaceFirst("\\.vcf$", "");
+
+        mSetPathQueue = new ArrayDeque<String>(Arrays.asList(dst.split("/")));
+
+        mProfileService.getPbapClient().setPhoneBookFolderRoot();
+    }
+
+    private void setPhonebookFolder(String folder) {
+        mProfileService.getPbapClient().setPhoneBookFolderDown(folder);
+    }
+
+    public void onClick_abort(View v) {
+        mProfileService.getPbapClient().abort();
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/ProfileService.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/ProfileService.java
new file mode 100644
index 0000000..79d6d23
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/ProfileService.java
@@ -0,0 +1,801 @@
+/*
+ * 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.bttestapp;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+//import android.bluetooth.BluetoothHandsfreeClient;
+import android.bluetooth.BluetoothMasInstance;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapBmessage;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapEventReport;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapMessage;
+import org.codeaurora.bluetooth.mapclient.BluetoothMasClient;
+import org.codeaurora.bluetooth.pbapclient.BluetoothPbapCard;
+import org.codeaurora.bluetooth.pbapclient.BluetoothPbapClient;
+import org.codeaurora.bluetooth.bttestapp.R;
+import org.codeaurora.bluetooth.bttestapp.services.IMapServiceCallback;
+import org.codeaurora.bluetooth.bttestapp.services.IPbapServiceCallback;
+import org.codeaurora.bluetooth.bttestapp.services.PbapAuthActivity;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class ProfileService extends Service {
+
+    private final static String TAG = "ProfileService";
+
+    private static final int PBAP_AUTH_NOTIFICATION_ID = 10000;
+
+    public static final String ACTION_HFP_CONNECTION_STATE = "org.codeaurora.bluetooth.action.HFP_CONNECTION_STATE";
+
+    public static final String ACTION_PBAP_CONNECTION_STATE = "org.codeaurora.bluetooth.action.PBAP_CONNECTION_STATE";
+
+    public static final String ACTION_MAP_CONNECTION_STATE = "org.codeaurora.bluetooth.action.MAP_CONNECTION_STATE";
+
+    public static final String ACTION_MAP_NOTIFICATION_STATE = "org.codeaurora.bluetooth.action.MAP_NOTIFICATION_STATE";
+
+    public static final String EXTRA_CONNECTED = "org.codeaurora.bluetooth.extra.CONNECTED";
+
+    public static final String EXTRA_NOTIFICATION_STATE = "org.codeaurora.bluetooth.extra.NOTIFICATION_STATE";
+
+    public static final String PBAP_AUTH_ACTION_REQUEST = "org.codeaurora.bluetooth.PBAP_AUTH_ACTION_REQUEST";
+
+    public static final String PBAP_AUTH_ACTION_CANCEL = "org.codeaurora.bluetooth.PBAP_AUTH_ACTION_CANCEL";
+
+    public static final String PBAP_AUTH_ACTION_RESPONSE = "org.codeaurora.bluetooth.PBAP_AUTH_ACTION_RESPONSE";
+
+    public static final String PBAP_AUTH_ACTION_TIMEOUT = "org.codeaurora.bluetooth.PBAP_AUTH_ACTION_TIMEOUT";
+
+    public static final String PBAP_AUTH_EXTRA_KEY = "org.codeaurora.bluetooth.PBAP_AUTH_EXTRA_KEY";
+
+    public static final String ACTION_MAP_GET_MESSAGE = "org.codeaurora.bluetooth.action.MAP_GET_MESSAGE";
+
+    public static final String EXTRA_MAP_INSTANCE_ID = "org.codeaurora.bluetooth.extra.MAP_INSTANCE_ID";
+
+    public static final String EXTRA_MAP_MESSAGE_HANDLE = "org.codeaurora.bluetooth.extra.MAP_MESSAGE_HANDLE";
+
+    private BluetoothDevice mDevice = null;
+
+    private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+    //private BluetoothHandsfreeClient mHfpClient = null;
+
+    private BluetoothPbapClient mPbapClient = null;
+
+    private IPbapServiceCallback mPbapCallback = null;
+
+    private final PbapSessionData mPbapSessionData = new PbapSessionData();
+
+    private HashMap<Integer, BluetoothMasClient> mMapClients = null;
+
+    private HashMap<Integer, IMapServiceCallback> mMapCallbacks = null;
+
+    private HashMap<Integer, MapSessionData> mMapSessionData = null;
+
+    private MapNotificationSender mMapNotificationSender = null;
+
+    private boolean mIsBound = false;
+
+    private final IBinder mBinder = new LocalBinder();
+
+    class PbapSessionData {
+        ArrayList<VCardEntry> pullPhoneBook = null;
+        ArrayList<BluetoothPbapCard> pullVcardListing = null;
+        VCardEntry pullVcardEntry = null;
+    }
+
+    class MapSessionData {
+        ArrayList<String> getFolderListing;
+        ArrayList<BluetoothMapMessage> getMessagesListing;
+        BluetoothMapBmessage getMessage;
+    }
+
+    public class LocalBinder extends Binder {
+        ProfileService getService() {
+            return ProfileService.this;
+        }
+    }
+
+    private final Handler mPbapHandler = new Handler() {
+
+        @Override
+        public void handleMessage(Message msg) {
+            Intent intent = null;
+
+            switch (msg.what) {
+                case BluetoothPbapClient.EVENT_SESSION_CONNECTED:
+                    intent = new Intent(ACTION_PBAP_CONNECTION_STATE);
+                    intent.putExtra(EXTRA_CONNECTED, true);
+                    break;
+
+                case BluetoothPbapClient.EVENT_SESSION_DISCONNECTED:
+                    intent = new Intent(ACTION_PBAP_CONNECTION_STATE);
+                    intent.putExtra(EXTRA_CONNECTED, false);
+                    break;
+            }
+
+            if (intent != null) {
+                ProfileService.this.sendBroadcast(intent);
+            }
+
+            switch (msg.what) {
+                case BluetoothPbapClient.EVENT_PULL_PHONE_BOOK_DONE:
+                    mPbapSessionData.pullPhoneBook = (ArrayList<VCardEntry>) msg.obj;
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_VCARD_LISTING_DONE:
+                    mPbapSessionData.pullVcardListing = (ArrayList<BluetoothPbapCard>) msg.obj;
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_VCARD_ENTRY_DONE:
+                    mPbapSessionData.pullVcardEntry = (VCardEntry) msg.obj;
+                    break;
+            }
+
+            if (mPbapCallback == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case BluetoothPbapClient.EVENT_SET_PHONE_BOOK_DONE:
+                    mPbapCallback.onSetPhoneBookDone();
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_PHONE_BOOK_DONE:
+                    mPbapCallback.onPullPhoneBookDone(mPbapSessionData.pullPhoneBook, msg.arg1);
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_VCARD_LISTING_DONE:
+                    mPbapCallback.onPullVcardListingDone(mPbapSessionData.pullVcardListing,
+                            msg.arg1);
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_VCARD_ENTRY_DONE:
+                    mPbapCallback.onPullVcardEntryDone(mPbapSessionData.pullVcardEntry);
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_PHONE_BOOK_SIZE_DONE:
+                    mPbapCallback.onPullPhoneBookSizeDone(msg.arg1, 0);
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_VCARD_LISTING_SIZE_DONE:
+                    mPbapCallback.onPullPhoneBookSizeDone(msg.arg1, 1);
+                    break;
+                case BluetoothPbapClient.EVENT_SET_PHONE_BOOK_ERROR:
+                    mPbapCallback.onSetPhoneBookError();
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_PHONE_BOOK_ERROR:
+                    mPbapCallback.onPullPhoneBookError();
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_VCARD_LISTING_ERROR:
+                    mPbapCallback.onPullVcardListingError();
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_VCARD_ENTRY_ERROR:
+                    mPbapCallback.onPullVcardEntryError();
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_PHONE_BOOK_SIZE_ERROR:
+                    mPbapCallback.onPullPhoneBookSizeError();
+                    break;
+                case BluetoothPbapClient.EVENT_PULL_VCARD_LISTING_SIZE_ERROR:
+                    mPbapCallback.onPullVcardListingSizeError();
+                    break;
+                case BluetoothPbapClient.EVENT_SESSION_CONNECTED:
+                    mPbapCallback.onSessionConnected();
+                    break;
+                case BluetoothPbapClient.EVENT_SESSION_DISCONNECTED:
+                    mPbapCallback.onSessionDisconnected();
+                    break;
+                case BluetoothPbapClient.EVENT_SESSION_AUTH_REQUESTED:
+                    createPbapAuthNotification();
+                    break;
+                case BluetoothPbapClient.EVENT_SESSION_AUTH_TIMEOUT:
+                    removePbapAuthNotification();
+                    break;
+
+                default:
+                    Log.w(TAG, "Unknown message in PBAP handler: " + msg.what);
+                    break;
+            }
+        }
+    };
+
+    private final Handler mMapHandler = new Handler() {
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public void handleMessage(Message msg) {
+            IMapServiceCallback cb = mMapCallbacks.get(msg.arg2);
+
+            Log.v(TAG, "mMapHandler::handleMessage msg=" + msg.what + " status=" + msg.arg1
+                    + " instanceid=" + msg.arg2);
+
+            boolean success = (msg.arg1 == BluetoothMasClient.STATUS_OK);
+
+            if (msg.what == BluetoothMasClient.EVENT_CONNECT) {
+                Intent intent = new Intent(ACTION_MAP_CONNECTION_STATE);
+                intent.putExtra(BluetoothDevice.EXTRA_MAS_INSTANCE, new BluetoothMasInstance(
+                        msg.arg2, null, 0, 0));
+                intent.putExtra(EXTRA_CONNECTED, success);
+
+                ProfileService.this.sendBroadcast(intent);
+            } else if (msg.what == BluetoothMasClient.EVENT_SET_NOTIFICATION_REGISTRATION) {
+                Intent intent = new Intent(ACTION_MAP_NOTIFICATION_STATE);
+                intent.putExtra(BluetoothDevice.EXTRA_MAS_INSTANCE, new BluetoothMasInstance(
+                        msg.arg2, null, 0, 0));
+                intent.putExtra(EXTRA_NOTIFICATION_STATE, ((Integer) msg.obj).intValue() != 0);
+
+                ProfileService.this.sendBroadcast(intent);
+            } else if (msg.what == BluetoothMasClient.EVENT_EVENT_REPORT) {
+                BluetoothMapEventReport evt = (BluetoothMapEventReport) msg.obj;
+                mMapNotificationSender.notify(msg.arg2, evt);
+            }
+
+            MapSessionData sessionData = mMapSessionData.get(msg.arg2);
+            if (sessionData == null) {
+                sessionData = new MapSessionData();
+                mMapSessionData.put(msg.arg2, sessionData);
+            }
+
+            if (success) {
+                switch (msg.what) {
+                    case BluetoothMasClient.EVENT_GET_FOLDER_LISTING:
+                        sessionData.getFolderListing = (ArrayList<String>) msg.obj;
+                        break;
+                    case BluetoothMasClient.EVENT_GET_MESSAGES_LISTING:
+                        sessionData.getMessagesListing = (ArrayList<BluetoothMapMessage>) msg.obj;
+                        break;
+                    case BluetoothMasClient.EVENT_GET_MESSAGE:
+                        sessionData.getMessage = (BluetoothMapBmessage) msg.obj;
+                        break;
+                }
+            }
+
+            if (cb == null) {
+                Log.w(TAG, "No callback registered for MAS instance " + msg.arg2);
+                return;
+            }
+
+            switch (msg.what) {
+                case BluetoothMasClient.EVENT_CONNECT:
+                    if (success) {
+                        cb.onConnect();
+                    } else {
+                        cb.onConnectError();
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_UPDATE_INBOX:
+                    if (success) {
+                        cb.onUpdateInbox();
+                    } else {
+                        cb.onUpdateInboxError();
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_SET_PATH:
+                    if (success) {
+                        cb.onSetPath((String) msg.obj);
+                    } else {
+                        cb.onSetPathError((String) msg.obj);
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_GET_FOLDER_LISTING:
+                    if (success && msg.obj instanceof ArrayList<?>) {
+                        cb.onGetFolderListing(sessionData.getFolderListing);
+                    } else {
+                        cb.onGetFolderListingError();
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_GET_FOLDER_LISTING_SIZE:
+                    if (success && msg.obj instanceof Integer) {
+                        cb.onGetFolderListingSize((Integer) msg.obj);
+                    } else {
+                        cb.onGetFolderListingSizeError();
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_GET_MESSAGES_LISTING:
+                    if (success && msg.obj instanceof ArrayList<?>) {
+                        cb.onGetMessagesListing(sessionData.getMessagesListing);
+                    } else {
+                        cb.onGetMessagesListingError();
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_GET_MESSAGE:
+                    if (success && msg.obj instanceof BluetoothMapBmessage) {
+                        cb.onGetMessage(sessionData.getMessage);
+                    } else {
+                        cb.onGetMessageError();
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_SET_MESSAGE_STATUS:
+                    if (success) {
+                        cb.onSetMessageStatus();
+                    } else {
+                        cb.onSetMessageStatusError();
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_PUSH_MESSAGE:
+                    if (success && msg.obj instanceof String) {
+                        cb.onPushMessage((String) msg.obj);
+                    } else {
+                        cb.onPushMessageError();
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_GET_MESSAGES_LISTING_SIZE:
+                    if (success && msg.obj instanceof Integer) {
+                        cb.onGetMessagesListingSize((Integer) msg.obj);
+                    } else {
+                        cb.onGetMessagesListingSizeError();
+                    }
+                    break;
+                case BluetoothMasClient.EVENT_EVENT_REPORT:
+                    BluetoothMapEventReport evt = (BluetoothMapEventReport) msg.obj;
+                    cb.onEventReport(evt);
+                    break;
+                default:
+                    Log.w(TAG, "Unknown message in MAP: " + msg.what);
+            }
+
+        }
+    };
+
+    public class MapNotificationSender {
+
+        public final int NEW_MESSAGE_NOTIFICATION_ID = 20000;
+
+        public final int DELIVERY_SUCCESS_NOTIFICATION_ID = 20001;
+
+        public final int SENDING_SUCCESS_NOTIFICATION_ID = 20002;
+
+        public final int DELIVERY_FAILURE_NOTIFICATION_ID = 20003;
+
+        public final int SENDING_FAILURE_NOTIFICATION_ID = 20004;
+
+        public final int MEMORY_FULL_NOTIFICATION_ID = 20005;
+
+        public final int MEMORY_AVAILABLE_NOTIFICATION_ID = 20006;
+
+        public final int MESSAGE_DELETED_NOTIFICATION_ID = 20007;
+
+        public final int MESSAGE_SHIFT_NOTIFICATION_ID = 20008;
+
+        private final NotificationManager mNotificationManager;
+
+        MapNotificationSender() {
+            mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        }
+
+        public void notify(int instanceId, BluetoothMapEventReport eventReport) {
+            switch (eventReport.getType()) {
+                case NEW_MESSAGE:
+                    notifyNewMessage(instanceId, eventReport);
+                    break;
+                case DELIVERY_SUCCESS:
+                    notifyDeliverySuccess(eventReport);
+                    break;
+                case SENDING_SUCCESS:
+                    notifySendingSuccess(eventReport);
+                    break;
+                case DELIVERY_FAILURE:
+                    notifyDeliveryFailure(eventReport);
+                    break;
+                case SENDING_FAILURE:
+                    notifySendingFailure(eventReport);
+                    break;
+                case MEMORY_AVAILABLE:
+                    notifyMemoryAvailable(eventReport);
+                    break;
+                case MEMORY_FULL:
+                    notifyMemoryFull(eventReport);
+                    break;
+                case MESSAGE_DELETED:
+                    notifyMessageDeleted(eventReport);
+                    break;
+                case MESSAGE_SHIFT:
+                    notifyMessageShift(eventReport);
+                    break;
+                default:
+                    Log.e(TAG, "Unknown MAP report type (" + eventReport.getType().toString()
+                            + ")!");
+                    break;
+            }
+        }
+
+        private void send(int id, String title, String text) {
+            send(id, title, text, null);
+        }
+
+        private void send(int id, String title, String text, Intent click) {
+            Notification.Builder builder = new Notification.Builder(getApplicationContext())
+                    .setContentTitle(title)
+                    .setContentText(text)
+                    .setTicker(getString(R.string.map_report_notif_ticker))
+                    .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+                    .setDefaults(Notification.DEFAULT_SOUND)
+                    .setAutoCancel(true)
+                    .setOnlyAlertOnce(true);
+
+            if (click != null) {
+                builder.setContentIntent(PendingIntent.getActivity(ProfileService.this, 0, click,
+                        PendingIntent.FLAG_UPDATE_CURRENT));
+            }
+
+            mNotificationManager.notify(id, builder.build());
+        }
+
+        private void notifyNewMessage(int instanceId, BluetoothMapEventReport eventReport) {
+            Intent click = new Intent(ProfileService.this, MapTestActivity.class);
+            click.setAction(ACTION_MAP_GET_MESSAGE);
+            click.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            click.putExtra(EXTRA_MAP_INSTANCE_ID, instanceId);
+            click.putExtra(EXTRA_MAP_MESSAGE_HANDLE, eventReport.getHandle());
+
+            send(NEW_MESSAGE_NOTIFICATION_ID,
+                    String.format(getString(R.string.map_report_notif_received, eventReport
+                            .getMsgType().toString())),
+                    String.format(getString(R.string.map_report_notif_handle,
+                            eventReport.getHandle())),
+                    click);
+        }
+
+        private void notifyDeliverySuccess(BluetoothMapEventReport eventReport) {
+            send(DELIVERY_SUCCESS_NOTIFICATION_ID,
+                    getString(R.string.map_report_notif_title_delivery_success),
+                    String.format(getString(R.string.map_report_notif_handle,
+                            eventReport.getHandle())));
+        }
+
+        private void notifySendingSuccess(BluetoothMapEventReport eventReport) {
+            send(SENDING_SUCCESS_NOTIFICATION_ID,
+                    getString(R.string.map_report_notif_title_sending_success),
+                    String.format(getString(R.string.map_report_notif_handle,
+                            eventReport.getHandle())));
+        }
+
+        private void notifyDeliveryFailure(BluetoothMapEventReport eventReport) {
+            send(DELIVERY_FAILURE_NOTIFICATION_ID,
+                    getString(R.string.map_report_notif_title_delivery_failure),
+                    String.format(getString(R.string.map_report_notif_handle,
+                            eventReport.getHandle())));
+        }
+
+        private void notifySendingFailure(BluetoothMapEventReport eventReport) {
+            send(SENDING_FAILURE_NOTIFICATION_ID,
+                    getString(R.string.map_report_notif_title_sending_failure),
+                    String.format(getString(R.string.map_report_notif_handle,
+                            eventReport.getHandle())));
+        }
+
+        private void notifyMemoryFull(BluetoothMapEventReport eventReport) {
+            send(MEMORY_FULL_NOTIFICATION_ID,
+                    getString(R.string.map_report_notif_title_memory_full),
+                    getString(R.string.blank));
+        }
+
+        private void notifyMemoryAvailable(BluetoothMapEventReport eventReport) {
+            send(MEMORY_AVAILABLE_NOTIFICATION_ID,
+                    getString(R.string.map_report_notif_title_memory_available),
+                    getString(R.string.blank));
+        }
+
+        private void notifyMessageDeleted(BluetoothMapEventReport eventReport) {
+            send(MESSAGE_DELETED_NOTIFICATION_ID,
+                    getString(R.string.map_report_notif_title_message_deleted),
+                    String.format(getString(R.string.map_report_notif_handle,
+                            eventReport.getHandle())));
+        }
+
+        private void notifyMessageShift(BluetoothMapEventReport eventReport) {
+            send(MESSAGE_SHIFT_NOTIFICATION_ID,
+                    String.format(getString(R.string.map_report_notif_shifted,
+                            eventReport.getHandle())),
+                    String.format(getString(R.string.map_report_notif_fromto,
+                            eventReport.getOldFolder(), eventReport.getFolder())));
+        }
+    }
+
+    BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.d(TAG, "received " + action);
+
+            /*if (BluetoothHandsfreeClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+                Intent new_intent = new Intent(ACTION_HFP_CONNECTION_STATE);
+
+                if (state == BluetoothProfile.STATE_CONNECTED) {
+                    new_intent.putExtra(EXTRA_CONNECTED, true);
+                } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
+                    new_intent.putExtra(EXTRA_CONNECTED, false);
+                } else {
+                    return;
+                }
+
+                ProfileService.this.sendBroadcast(new_intent);
+            } else */if (PBAP_AUTH_ACTION_RESPONSE.equals(action)) {
+                String key = intent.getStringExtra(PBAP_AUTH_EXTRA_KEY);
+                mPbapClient.setAuthResponse(key);
+            } else if (PBAP_AUTH_ACTION_CANCEL.equals(action)) {
+                mPbapClient.setAuthResponse(null);
+            } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
+                BluetoothDevice dev = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+                if (dev.equals(mDevice)) {
+                    if (mPbapClient != null) {
+                        mPbapClient.disconnect();
+                    }
+
+                    for (BluetoothMasClient cli : mMapClients.values()) {
+                        cli.disconnect();
+                    }
+
+                    checkAndStop(false, true);
+                }
+            }
+        }
+    };
+
+/*    private final ServiceListener mHfpServiceListener = new ServiceListener() {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (profile == BluetoothProfile.HANDSFREE_CLIENT) {
+                mHfpClient = (BluetoothHandsfreeClient) proxy;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (profile == BluetoothProfile.HANDSFREE_CLIENT) {
+                mHfpClient = null;
+            }
+        }
+    };
+*/
+    private void createPbapAuthNotification() {
+        NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+        Intent click = new Intent(this, PbapAuthActivity.class);
+        click.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        click.setAction(PBAP_AUTH_ACTION_REQUEST);
+        click.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+
+        Intent delete = new Intent(this, PbapAuthActivity.class);
+        delete.setAction(PBAP_AUTH_ACTION_CANCEL);
+
+        Notification no = new Notification.Builder(getApplicationContext())
+                .setContentTitle(getString(R.string.auth_notif_title))
+                .setContentText(getString(R.string.auth_notif_message, mDevice.getName()))
+                .setTicker(getString(R.string.auth_notif_ticker))
+                .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+                .setContentIntent(PendingIntent.getActivity(this, 0, click, 0))
+                .setDeleteIntent(PendingIntent.getBroadcast(this, 0, delete, 0))
+                .setAutoCancel(true)
+                .setOnlyAlertOnce(true)
+                .setDefaults(Notification.DEFAULT_SOUND)
+                .build();
+
+        nm.notify(PBAP_AUTH_NOTIFICATION_ID, no);
+    }
+
+    private void removePbapAuthNotification() {
+        NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+        nm.cancel(PBAP_AUTH_NOTIFICATION_ID);
+
+        Intent intent = new Intent(PBAP_AUTH_ACTION_TIMEOUT);
+        sendBroadcast(intent);
+    }
+
+    private void checkAndStop(boolean unbind, boolean disconnect) {
+        boolean canStop = true;
+
+        Log.v(TAG, "checkAndStop(): unbind=" + unbind + " disconnect=" + disconnect);
+
+        if (unbind) {
+            /*if (mHfpClient != null &&
+                    mHfpClient.getConnectionState(mDevice) != BluetoothProfile.STATE_DISCONNECTED) {
+                canStop = false;
+            }*/
+
+            if (mPbapClient != null
+                    && mPbapClient.getState() != BluetoothPbapClient.ConnectionState.DISCONNECTED) {
+                canStop = false;
+            }
+
+            for (BluetoothMasClient cli : mMapClients.values()) {
+                if (cli.getState() != BluetoothMasClient.ConnectionState.DISCONNECTED) {
+                    canStop = false;
+                }
+            }
+
+            if (!canStop) {
+                Log.v(TAG, "clients are still connected, won't stop");
+            }
+        }
+
+        if (disconnect && mIsBound) {
+            canStop = false;
+            Log.v(TAG, "service is still bound, won't stop");
+        }
+
+        if (canStop) {
+            stopSelf();
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        mIsBound = true;
+
+        return mBinder;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mIsBound = false;
+
+        checkAndStop(true, false);
+
+        return false;
+    }
+
+    @Override
+    public void onCreate() {
+        Log.v(TAG, "onCreate");
+
+        mMapClients = new HashMap<Integer, BluetoothMasClient>();
+        mMapCallbacks = new HashMap<Integer, IMapServiceCallback>();
+        mMapSessionData = new HashMap<Integer, MapSessionData>();
+        mMapNotificationSender = new MapNotificationSender();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(PBAP_AUTH_ACTION_RESPONSE);
+        filter.addAction(PBAP_AUTH_ACTION_CANCEL);
+        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+        //filter.addAction(BluetoothHandsfreeClient.ACTION_CONNECTION_STATE_CHANGED);
+        registerReceiver(mReceiver, filter);
+
+//        mAdapter.getProfileProxy(getApplicationContext(), mHfpServiceListener,
+  //              BluetoothProfile.HANDSFREE_CLIENT);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.v(TAG, "onStartCommand intent=" + intent + " flags=" + Integer.toHexString(flags)
+                + " startId=" + startId);
+        return START_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.v(TAG, "onDestroy");
+
+        //mAdapter.closeProfileProxy(BluetoothProfile.HANDSFREE_CLIENT,
+          //      mHfpClient);
+
+        unregisterReceiver(mReceiver);
+
+        if (mPbapClient != null) {
+            mPbapClient.disconnect();
+        }
+
+        for (BluetoothMasClient cli : mMapClients.values()) {
+            cli.disconnect();
+        }
+    }
+
+    public void setDevice(BluetoothDevice device) {
+        if (mDevice != null && mDevice.equals(device)) {
+            return;
+        }
+
+        /*if (mHfpClient != null) {
+            mHfpClient.disconnect(mDevice);
+        }*/
+
+        if (mPbapClient != null) {
+            mPbapClient.disconnect();
+        }
+
+        for (BluetoothMasClient cli : mMapClients.values()) {
+            cli.disconnect();
+        }
+
+        mDevice = device;
+
+        if (mDevice != null) {
+            Log.v(TAG,
+                    "Current device: address=" + mDevice.getAddress() + " name="
+                            + mDevice.getName());
+        } else {
+            Log.v(TAG, "Current device: none");
+        }
+
+        mPbapClient = null;
+        mMapClients = new HashMap<Integer, BluetoothMasClient>();
+        mMapSessionData = new HashMap<Integer, MapSessionData>();
+    }
+
+    /*public BluetoothHandsfreeClient getHfpClient() {
+        return mHfpClient;
+    }*/
+
+    public BluetoothPbapClient getPbapClient() {
+        if (mDevice == null) {
+            return null;
+        }
+
+        if (mPbapClient == null) {
+            mPbapClient = new BluetoothPbapClient(mDevice, mPbapHandler);
+        }
+
+        return mPbapClient;
+    }
+
+    public void setPbapCallback(IPbapServiceCallback callback) {
+        mPbapCallback = callback;
+    }
+
+    public PbapSessionData getPbapSessionData() {
+        return mPbapSessionData;
+    }
+
+    public void setMasInstances(ArrayList<BluetoothMasInstance> instances) {
+        for (BluetoothMasInstance inst : instances) {
+            // no need to recreate already existing MAS client
+            if (mMapClients.containsKey(inst.getId())) {
+                continue;
+            }
+
+            BluetoothMasClient client = new BluetoothMasClient(mDevice, inst, mMapHandler);
+            mMapClients.put(inst.getId(), client);
+        }
+    }
+
+    public BluetoothMasClient getMapClient(int id) {
+        return mMapClients.get(id);
+    }
+
+    public void setMapCallback(int id, IMapServiceCallback callback) {
+        mMapCallbacks.put(id, callback);
+    }
+
+    public MapSessionData getMapSessionData(int id) {
+        return mMapSessionData.get(id);
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/ServicesFragment.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/ServicesFragment.java
new file mode 100644
index 0000000..df72d43
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/ServicesFragment.java
@@ -0,0 +1,611 @@
+/*
+ * 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.bttestapp;
+
+import android.app.Activity;
+import android.app.ListFragment;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+//import android.bluetooth.BluetoothHandsfreeClient;
+import android.bluetooth.BluetoothMasInstance;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import org.codeaurora.bluetooth.mapclient.BluetoothMasClient;
+import org.codeaurora.bluetooth.pbapclient.BluetoothPbapClient;
+import org.codeaurora.bluetooth.bttestapp.R;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class ServicesFragment extends ListFragment {
+
+    private final static String TAG = "ServicesFragment";
+
+    private MainActivity mActivity;
+
+    private ServicesAdapter mAdapter;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            boolean connState = false;
+            boolean notifState = false;
+            boolean connEvent = false;
+            boolean notifEvent = false;
+            int idx = -1;
+
+            Log.v(TAG, "mReceiver got " + action);
+
+    /*        if (ProfileService.ACTION_HFP_CONNECTION_STATE.equals(action)) {
+                connState = intent.getBooleanExtra(ProfileService.EXTRA_CONNECTED, false);
+
+                Service srv = new Service(Service.Type.HFP, null);
+                idx = mAdapter.getItemPos(srv);
+
+                if (idx < 0) {
+                    Log.w(TAG, "Cannot find HFP service item");
+                }
+
+                connEvent = true;
+
+            } else */if (ProfileService.ACTION_PBAP_CONNECTION_STATE.equals(action)) {
+                connState = intent.getBooleanExtra(ProfileService.EXTRA_CONNECTED, false);
+
+                Service srv = new Service(Service.Type.PBAP, null);
+                idx = mAdapter.getItemPos(srv);
+
+                if (idx < 0) {
+                    Log.w(TAG, "Cannot find PBAP service item");
+                }
+
+                connEvent = true;
+
+            } else if (ProfileService.ACTION_MAP_CONNECTION_STATE.equals(action)) {
+                connState = intent.getBooleanExtra(ProfileService.EXTRA_CONNECTED, false);
+                BluetoothMasInstance inst = intent
+                        .getParcelableExtra(BluetoothDevice.EXTRA_MAS_INSTANCE);
+
+                Service srv = new Service(Service.Type.MAP, inst);
+                idx = mAdapter.getItemPos(srv);
+
+                if (idx < 0) {
+                    Log.w(TAG, "Cannot find MAP service item");
+                }
+
+                connEvent = true;
+
+            } else if (ProfileService.ACTION_MAP_NOTIFICATION_STATE.equals(action)) {
+                notifState = intent.getBooleanExtra(ProfileService.EXTRA_NOTIFICATION_STATE, false);
+                BluetoothMasInstance inst = intent
+                        .getParcelableExtra(BluetoothDevice.EXTRA_MAS_INSTANCE);
+
+                Service srv = new Service(Service.Type.MAP, inst);
+                idx = mAdapter.getItemPos(srv);
+
+                if (idx < 0) {
+                    Log.w(TAG, "Cannot find MAP service item");
+                }
+
+                notifEvent = true;
+
+            }
+
+            if (idx < 0) {
+                return;
+            }
+
+            View v = (ServicesFragment.this).getListView().getChildAt(idx);
+            if (v != null) {
+                Switch swSrv = (Switch) v.findViewById(R.id.service_switch);
+                Switch swNotif = (Switch) v.findViewById(R.id.notification_switch);
+
+                if (connEvent) {
+                    swSrv.setChecked(connState);
+                    swSrv.setEnabled(true);
+                    swNotif.setEnabled(connState);
+                    swNotif.setChecked(false);
+                }
+                if (notifEvent) {
+                    swNotif.setChecked(notifState);
+                    swNotif.setEnabled(true);
+                }
+            }
+        }
+    };
+
+    static final class Service {
+
+        final Type mType;
+
+        final BluetoothMasInstance mMasInstance;
+
+        enum Type {
+       //     HFP("Hands-Free Profile (AG)"),
+            PBAP("Phone Book Access Profile (PSE)"),
+            MAP("Message Access Profile (MSE)");
+
+            final String mTitle;
+
+            Type(String title) {
+                mTitle = title;
+            }
+        }
+
+        Service(Type type, BluetoothMasInstance masInstance) {
+            mType = type;
+            mMasInstance = masInstance;
+        }
+
+        @Override
+        public boolean equals(Object srv) {
+            if (!mType.equals(((Service) srv).mType)) {
+                return false;
+            }
+
+            if (mType.equals(Type.MAP)) {
+                return mMasInstance.equals(((Service) srv).mMasInstance);
+            }
+
+            return true;
+        }
+    }
+
+    private class ServicesAdapter extends BaseAdapter implements ListAdapter,
+            OnCheckedChangeListener {
+
+        private final LayoutInflater mInflater;
+
+        private final ArrayList<Service> mServices = new ArrayList<Service>();
+
+        public ServicesAdapter(Context context) {
+            super();
+            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        @Override
+        public int getCount() {
+            return mServices.size();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mServices.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View v = convertView;
+            boolean bluetoothOn = BluetoothAdapter.getDefaultAdapter().isEnabled();
+
+            Log.v(TAG, "getView");
+            if (v == null) {
+                v = mInflater.inflate(R.layout.service_row, parent, false);
+            }
+
+            Service srv = mServices.get(position);
+
+            TextView txtName = (TextView) v.findViewById(R.id.service_name);
+            txtName.setText(srv.mType.mTitle);
+
+            TextView txtTitle = (TextView) v.findViewById(R.id.service_title);
+            if (srv.mType.equals(Service.Type.MAP)) {
+                txtTitle.setText(srv.mMasInstance.getName());
+            } else {
+                txtTitle.setText("");
+            }
+
+            Switch swSrv = (Switch) v.findViewById(R.id.service_switch);
+            swSrv.setTag(Integer.valueOf(position));
+            swSrv.setOnCheckedChangeListener(this);
+
+            Switch swNotif = (Switch) v.findViewById(R.id.notification_switch);
+            if (srv.mType.equals(Service.Type.MAP)) {
+                swNotif.setVisibility(View.VISIBLE);
+                swNotif.setTag(Integer.valueOf(position));
+                swNotif.setOnCheckedChangeListener(this);
+            } else {
+                swNotif.setVisibility(View.INVISIBLE);
+            }
+
+            // need to update switch state on new view
+            if (mActivity.mProfileService != null) {
+                switch (srv.mType) {
+                  /*  case HFP: {
+                        BluetoothHandsfreeClient cli = mActivity.mProfileService.getHfpClient();
+
+                        if (cli == null || bluetoothOn == false) {
+                            swSrv.setChecked(false);
+                            swSrv.setEnabled(false);
+                            break;
+                        }
+
+                        switch (cli.getConnectionState(mActivity.mDevice)) {
+                            case BluetoothProfile.STATE_DISCONNECTED:
+                                swSrv.setChecked(false);
+                                swSrv.setEnabled(true);
+                                break;
+                            case BluetoothProfile.STATE_CONNECTING:
+                                swSrv.setChecked(true);
+                                swSrv.setEnabled(false);
+                                break;
+                            case BluetoothProfile.STATE_CONNECTED:
+                                swSrv.setChecked(true);
+                                swSrv.setEnabled(true);
+                                break;
+                            case BluetoothProfile.STATE_DISCONNECTING:
+                                swSrv.setChecked(false);
+                                swSrv.setEnabled(false);
+                                break;
+                        }
+
+                        break;
+                    }
+*/
+                    case PBAP: {
+                        BluetoothPbapClient cli = mActivity.mProfileService.getPbapClient();
+
+                        if (cli == null || bluetoothOn == false) {
+                            swSrv.setChecked(false);
+                            swSrv.setEnabled(false);
+                            break;
+                        }
+
+                        switch (cli.getState()) {
+                            case DISCONNECTED:
+                                swSrv.setChecked(false);
+                                swSrv.setEnabled(true);
+                                break;
+                            case CONNECTING:
+                                swSrv.setChecked(true);
+                                swSrv.setEnabled(false);
+                                break;
+                            case CONNECTED:
+                                swSrv.setChecked(true);
+                                swSrv.setEnabled(true);
+                                break;
+                            case DISCONNECTING:
+                                swSrv.setChecked(false);
+                                swSrv.setEnabled(false);
+                                break;
+                        }
+
+                        break;
+                    }
+
+                    case MAP: {
+                        BluetoothMasClient cli = mActivity.mProfileService
+                                .getMapClient(srv.mMasInstance.getId());
+
+                        swNotif.setEnabled(false);
+
+                        if (cli == null || bluetoothOn == false) {
+                            swSrv.setChecked(false);
+                            swSrv.setEnabled(false);
+                            break;
+                        }
+
+                        switch (cli.getState()) {
+                            case DISCONNECTED:
+                                swSrv.setChecked(false);
+                                swSrv.setEnabled(true);
+                                break;
+                            case CONNECTING:
+                                swSrv.setChecked(true);
+                                swSrv.setEnabled(false);
+                                break;
+                            case CONNECTED:
+                                swSrv.setChecked(true);
+                                swSrv.setEnabled(true);
+                                swNotif.setEnabled(true);
+                                break;
+                            case DISCONNECTING:
+                                swSrv.setChecked(false);
+                                swSrv.setEnabled(false);
+                                break;
+                        }
+
+                        swNotif.setChecked(cli.getNotificationRegistration());
+
+                        break;
+                    }
+                }
+            }
+
+            return v;
+        }
+
+        public int getItemPos(Service srv) {
+            for (int i = 0; i < mServices.size(); i++) {
+                if (mServices.get(i).equals(srv)) {
+                    return i;
+                }
+            }
+
+            return -1;
+        }
+
+        public void addService(Service.Type type, BluetoothMasInstance masInstance) {
+            Service srv = new Service(type, masInstance);
+
+            if (!mServices.contains(srv)) {
+                mServices.add(srv);
+            }
+
+            notifyDataSetChanged();
+        }
+
+        public void removeService(Service.Type type) {
+            for (Iterator<Service> iter = mServices.iterator(); iter.hasNext();) {
+                Service srv = iter.next();
+
+                if (type == null || srv.mType.equals(type)) {
+                    iter.remove();
+                }
+            }
+
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+            Service srv = mServices.get((Integer) buttonView.getTag());
+
+            switch (srv.mType) {
+/*                case HFP:
+                    if (isChecked) {
+                        mActivity.mProfileService.getHfpClient().connect(mActivity.mDevice);
+                        buttonView.setEnabled(false);
+                    } else {
+                        mActivity.mProfileService.getHfpClient().disconnect(mActivity.mDevice);
+                        buttonView.setEnabled(false);
+                    }
+                    break;
+*/
+                case PBAP:
+                    if (isChecked) {
+                        mActivity.mProfileService.getPbapClient().connect();
+                        buttonView.setEnabled(false);
+                    } else {
+                        mActivity.mProfileService.getPbapClient().disconnect();
+                        buttonView.setEnabled(false);
+                    }
+                    break;
+
+                case MAP:
+                    BluetoothMasClient cli = mActivity.mProfileService
+                            .getMapClient(srv.mMasInstance.getId());
+                    if (isChecked) {
+                        if (buttonView.getId() == R.id.service_switch) {
+                            cli.connect();
+                        } else {
+                            cli.setNotificationRegistration(true);
+                        }
+                        buttonView.setEnabled(false);
+                    } else {
+                        if (buttonView.getId() == R.id.service_switch) {
+                            cli.disconnect();
+                        } else {
+                            cli.setNotificationRegistration(false);
+                        }
+                        buttonView.setEnabled(false);
+                    }
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        mAdapter = new ServicesAdapter(getActivity());
+        setListAdapter(mAdapter);
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mActivity = (MainActivity) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(this.getClass().getName()
+                    + " can only be attached to MainActivity!");
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ProfileService.ACTION_HFP_CONNECTION_STATE);
+        filter.addAction(ProfileService.ACTION_PBAP_CONNECTION_STATE);
+        filter.addAction(ProfileService.ACTION_MAP_CONNECTION_STATE);
+        filter.addAction(ProfileService.ACTION_MAP_NOTIFICATION_STATE);
+        getActivity().registerReceiver(mReceiver, filter);
+
+        mAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        getActivity().unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    public void onListItemClick(ListView lv, View v, int position, long id) {
+        Service srv = (Service) mAdapter.getItem(position);
+        Intent intent = null;
+
+        if (BluetoothAdapter.getDefaultAdapter().isEnabled() == false) return;
+
+        switch (srv.mType) {
+            case PBAP:
+                intent = new Intent(getActivity(), PbapTestActivity.class);
+                break;
+
+/*            case HFP:
+                intent = new Intent(getActivity(), HfpTestActivity.class);
+                break;
+*/
+            case MAP:
+                intent = new Intent(getActivity(), MapTestActivity.class);
+                intent.putExtra(ProfileService.EXTRA_MAP_INSTANCE_ID, srv.mMasInstance.getId());
+                break;
+
+            default:
+                // this should never happen!
+                throw new IllegalArgumentException();
+        }
+
+        startActivity(intent);
+    }
+
+    public void addService(Service.Type type, BluetoothMasInstance masInstance) {
+        mAdapter.addService(type, masInstance);
+    }
+
+    public void removeService(Service.Type type) {
+        mAdapter.removeService(type);
+    }
+
+    public void persistServices() {
+        SharedPreferences.Editor prefs = getActivity().getPreferences(Context.MODE_PRIVATE).edit();
+
+        JSONArray json = new JSONArray();
+
+        int cnt = mAdapter.getCount();
+        for (int i = 0; i < cnt; i++) {
+            Service srv = (Service) mAdapter.getItem(i);
+
+            try {
+                JSONObject jsrv = new JSONObject();
+
+                jsrv.put("type", srv.mType);
+
+                if (srv.mMasInstance != null) {
+                    jsrv.put("i_id", srv.mMasInstance.getId());
+                    jsrv.put("i_name", srv.mMasInstance.getName());
+                    jsrv.put("i_scn", srv.mMasInstance.getChannel());
+                    jsrv.put("i_msg", srv.mMasInstance.getMsgTypes());
+                }
+
+                json.put(jsrv);
+            } catch (JSONException e) {
+            }
+        }
+
+        prefs.putString(MainActivity.PREF_SERVICES, json.toString());
+
+        prefs.commit();
+    }
+
+    public void restoreServices() {
+        String str = getActivity().getPreferences(Context.MODE_PRIVATE).getString(
+                MainActivity.PREF_SERVICES, null);
+
+        ArrayList<BluetoothMasInstance> insts = new ArrayList<BluetoothMasInstance>();
+
+        if (str == null) {
+            return;
+        }
+
+        try {
+            Object obj = new JSONTokener(str).nextValue();
+
+            if (!(obj instanceof JSONArray)) {
+                return;
+            }
+
+            JSONArray json = (JSONArray) obj;
+
+            for (int i = 0; i < json.length(); i++) {
+                JSONObject jsrv = json.getJSONObject(i);
+
+                String stype = jsrv.getString("type");
+                Service.Type type = null;
+                BluetoothMasInstance inst = null;
+
+                for (Service.Type t : Service.Type.values()) {
+                    if (t.name().equals(stype)) {
+                        type = t;
+                        break;
+                    }
+                }
+
+                if (type == null) {
+                    continue;
+                }
+
+                if (type.equals(Service.Type.MAP)) {
+                    inst = new BluetoothMasInstance(jsrv.getInt("i_id"), jsrv.getString("i_name"),
+                            jsrv.getInt("i_scn"), jsrv.getInt("i_msg"));
+                    insts.add(inst);
+                }
+
+                mAdapter.addService(type, inst);
+            }
+        } catch (JSONException e) {
+        }
+
+        mActivity.mProfileService.setMasInstances(insts);
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/StringListDialogFragment.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/StringListDialogFragment.java
new file mode 100644
index 0000000..899ad7c
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/StringListDialogFragment.java
@@ -0,0 +1,112 @@
+/*
+ * 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.bttestapp;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+class StringListDialogFragment extends DialogFragment {
+
+    private final ArrayList<String> mElements;
+
+    private TreeSet<Integer> mSelectedItems;
+
+    private StringListDialogListener mListener;
+
+    public interface StringListDialogListener {
+        public void onStringListDialogPositive(DialogFragment dialog, ArrayList<String> elements);
+    };
+
+    public StringListDialogFragment(ArrayList<String> elements) {
+        super();
+
+        mElements = new ArrayList<String>(elements);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        mSelectedItems = new TreeSet<Integer>();
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+        CharSequence[] elements = mElements.toArray(new CharSequence[mElements.size()]);
+
+        builder.setTitle("Select items to remove")
+                .setMultiChoiceItems(elements, null,
+                        new DialogInterface.OnMultiChoiceClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+                                if (isChecked) {
+                                    mSelectedItems.add(which);
+                                } else if (mSelectedItems.contains(which)) {
+                                    mSelectedItems.remove(which);
+                                }
+                            }
+                        })
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        Iterator<Integer> iter = mSelectedItems.descendingIterator();
+
+                        while (iter.hasNext()) {
+                            int idx = iter.next();
+                            mElements.remove(idx);
+                        }
+
+                        mListener.onStringListDialogPositive(StringListDialogFragment.this,
+                                mElements);
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, null);
+
+        return builder.create();
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mListener = (StringListDialogListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement StringListDialogListener");
+        }
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/VcardFilterActivity.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/VcardFilterActivity.java
new file mode 100644
index 0000000..0c26762
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/VcardFilterActivity.java
@@ -0,0 +1,83 @@
+/*
+ * 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.bttestapp;
+
+import android.os.Bundle;
+
+import org.codeaurora.bluetooth.bttestapp.ProfileService;
+import org.codeaurora.bluetooth.bttestapp.util.Logger;
+
+public class VcardFilterActivity extends FilterActivity {
+
+    private final String TAG = "FilterChooserActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Logger.v(TAG, "onCreate()");
+        ActivityHelper.initialize(this, R.layout.activity_vcard_filter);
+        ActivityHelper.setActionBarTitle(this, R.string.title_vcard_filter);
+    }
+
+    @Override
+    protected void fillParameters() {
+        addParameter(0, R.id.filter_chooser_version);
+        addParameter(1, R.id.filter_chooser_fn);
+        addParameter(2, R.id.filter_chooser_n);
+        addParameter(3, R.id.filter_chooser_photo);
+        addParameter(4, R.id.filter_chooser_bday);
+        addParameter(5, R.id.filter_chooser_adr);
+        addParameter(6, R.id.filter_chooser_label);
+        addParameter(7, R.id.filter_chooser_tel);
+        addParameter(8, R.id.filter_chooser_email);
+        addParameter(9, R.id.filter_chooser_mailer);
+        addParameter(10, R.id.filter_chooser_tz);
+        addParameter(11, R.id.filter_chooser_geo);
+        addParameter(12, R.id.filter_chooser_title);
+        addParameter(13, R.id.filter_chooser_role);
+        addParameter(14, R.id.filter_chooser_logo);
+        addParameter(15, R.id.filter_chooser_agent);
+        addParameter(16, R.id.filter_chooser_org);
+        addParameter(17, R.id.filter_chooser_note);
+        addParameter(18, R.id.filter_chooser_rev);
+        addParameter(19, R.id.filter_chooser_sound);
+        addParameter(20, R.id.filter_chooser_url);
+        addParameter(21, R.id.filter_chooser_uid);
+        addParameter(22, R.id.filter_chooser_key);
+        addParameter(23, R.id.filter_chooser_nickname);
+        addParameter(24, R.id.filter_chooser_categories);
+        addParameter(25, R.id.filter_chooser_proid);
+        addParameter(26, R.id.filter_chooser_class);
+        addParameter(27, R.id.filter_chooser_sort_string);
+        addParameter(28, R.id.filter_chooser_x_irmc_call_datetime);
+        addParameter(39, R.id.filter_chooser_filter);
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/VcardView.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/VcardView.java
new file mode 100644
index 0000000..3ca4ded
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/VcardView.java
@@ -0,0 +1,138 @@
+/*
+ * 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.bttestapp;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.vcard.VCardEntry;
+import org.codeaurora.bluetooth.bttestapp.R;
+
+public class VcardView extends LinearLayout {
+
+    private final OnLongClickListener mLongClickListener = new OnLongClickListener() {
+
+        @Override
+        public boolean onLongClick(View v) {
+            String txt = ((TextView) v).getText().toString();
+
+            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
+                    Context.CLIPBOARD_SERVICE);
+
+            clipboard.setPrimaryClip(ClipData.newPlainText(txt, txt));
+
+            Toast.makeText(getContext(), "copied: " + txt, Toast.LENGTH_SHORT).show();
+
+            return true;
+        }
+    };
+
+    public VcardView(Context context) {
+        super(context);
+    }
+
+    public VcardView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public VcardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    private static VCardEntry mVCardEntry = null;
+
+    public void setVCardEntry(VCardEntry vcard) {
+        mVCardEntry = vcard;
+        populateUi();
+    }
+
+    private void addToContainer(ViewGroup cont, String str) {
+        TextView txt = new TextView(getContext());
+        txt.setText(str);
+        txt.setTextAppearance(getContext(), android.R.style.TextAppearance_Large);
+        txt.setPadding(3, 3, 3, 3);
+        txt.setOnLongClickListener(mLongClickListener);
+        cont.addView(txt);
+    }
+
+    private void populateUi() {
+        removeAllViewsInLayout();
+
+        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        View view = inflater.inflate(R.layout.vcard_view, this);
+
+        ((ImageView) view.findViewById(R.id.contact_photo))
+                .setImageResource(R.drawable.ic_contact_picture);
+
+        if (mVCardEntry.getPhotoList() != null && mVCardEntry.getPhotoList().size() > 0) {
+            byte[] data = mVCardEntry.getPhotoList().get(0).getBytes();
+            try {
+                if (data != null) {
+                    Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
+                    ((ImageView) view.findViewById(R.id.contact_photo)).setImageBitmap(bmp);
+                }
+            } catch (IllegalArgumentException e) {
+                // don't care, will just show dummy image
+            }
+        }
+
+        ((TextView) view.findViewById(R.id.contact_display_name)).setText(mVCardEntry
+                .getDisplayName());
+
+        if (mVCardEntry.getPhoneList() != null) {
+            LinearLayout cont = (LinearLayout) view.findViewById(R.id.contact_phonelist);
+
+            for (VCardEntry.PhoneData phone : mVCardEntry.getPhoneList()) {
+                addToContainer(cont, phone.getNumber());
+            }
+        }
+
+        if (mVCardEntry.getEmailList() != null) {
+            LinearLayout cont = (LinearLayout) view.findViewById(R.id.contact_emaillist);
+
+            for (VCardEntry.EmailData email : mVCardEntry.getEmailList()) {
+                addToContainer(cont, email.getAddress());
+            }
+        }
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/services/IMapServiceCallback.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/services/IMapServiceCallback.java
new file mode 100644
index 0000000..b1f4608
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/services/IMapServiceCallback.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.bttestapp.services;
+
+import org.codeaurora.bluetooth.mapclient.BluetoothMapBmessage;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapEventReport;
+import org.codeaurora.bluetooth.mapclient.BluetoothMapMessage;
+
+import java.util.ArrayList;
+
+public interface IMapServiceCallback {
+
+    public void onConnect();
+
+    public void onConnectError();
+
+    public void onUpdateInbox();
+
+    public void onUpdateInboxError();
+
+    public void onSetPath(String currentPath);
+
+    public void onSetPathError(String currentPath);
+
+    public void onGetFolderListing(ArrayList<String> folders);
+
+    public void onGetFolderListingError();
+
+    public void onGetFolderListingSize(int size);
+
+    public void onGetFolderListingSizeError();
+
+    public void onGetMessagesListing(ArrayList<BluetoothMapMessage> messages);
+
+    public void onGetMessagesListingError();
+
+    public void onGetMessage(BluetoothMapBmessage message);
+
+    public void onGetMessageError();
+
+    public void onSetMessageStatus();
+
+    public void onSetMessageStatusError();
+
+    public void onPushMessage(String handle);
+
+    public void onPushMessageError();
+
+    public void onGetMessagesListingSize(int size);
+
+    public void onGetMessagesListingSizeError();
+
+    public void onEventReport(BluetoothMapEventReport eventReport);
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/services/IPbapServiceCallback.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/services/IPbapServiceCallback.java
new file mode 100644
index 0000000..292ecc5
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/services/IPbapServiceCallback.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.bttestapp.services;
+
+import com.android.vcard.VCardEntry;
+import org.codeaurora.bluetooth.pbapclient.BluetoothPbapCard;
+
+import java.util.ArrayList;
+
+public interface IPbapServiceCallback {
+
+    void onSetPhoneBookDone();
+
+    void onPullPhoneBookDone(ArrayList<VCardEntry> list, int missedCalls);
+
+    void onPullVcardListingDone(ArrayList<BluetoothPbapCard> list, int missedCalls);
+
+    void onPullVcardEntryDone(VCardEntry vcard);
+
+    void onPullPhoneBookSizeDone(int size, int type);
+
+    void onSetPhoneBookError();
+
+    void onPullPhoneBookError();
+
+    void onPullVcardListingError();
+
+    void onPullVcardEntryError();
+
+    void onPullPhoneBookSizeError();
+
+    void onPullVcardListingSizeError();
+
+    void onSessionConnected();
+
+    void onSessionDisconnected();
+
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/services/PbapAuthActivity.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/services/PbapAuthActivity.java
new file mode 100644
index 0000000..4541c31
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/services/PbapAuthActivity.java
@@ -0,0 +1,249 @@
+/*
+ * 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.bttestapp.services;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import org.codeaurora.bluetooth.bttestapp.ProfileService;
+import org.codeaurora.bluetooth.bttestapp.R;
+
+public class PbapAuthActivity extends AlertActivity implements DialogInterface.OnClickListener,
+        TextWatcher {
+
+    private static final String TAG = "PbapAuthActivity";
+
+    private static final int KEY_MAX_LENGTH = 16;
+
+    private static final int TIMEOUT_MSG = 0;
+
+    private static final int TIMEOUT_VALUE_MS = 2000;
+
+    private static final String KEY_USER_TIMEOUT = "user_timeout";
+
+    private BluetoothDevice mDevice;
+
+    private Button mOkButton;
+
+    private EditText mKeyEditText;
+
+    private TextView mMessageTextView;
+
+    private String mKey;
+
+    private boolean mTimeout = false;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ProfileService.PBAP_AUTH_ACTION_TIMEOUT.equals(intent.getAction())) {
+                onTimeout();
+            }
+        }
+    };
+
+    private final Handler mTimeoutHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case TIMEOUT_MSG:
+                    finish();
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+
+        if (ProfileService.PBAP_AUTH_ACTION_REQUEST.equals(intent.getAction())) {
+            mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            showAuthDialog();
+
+            registerReceiver(mReceiver, new IntentFilter(ProfileService.PBAP_AUTH_ACTION_TIMEOUT));
+        } else {
+            Log.e(TAG, "This activity can be also started with AUTH_ACTION_REQUEST intent");
+            finish();
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case DialogInterface.BUTTON_POSITIVE:
+                mKey = mKeyEditText.getText().toString();
+                onPositive();
+                break;
+
+            case DialogInterface.BUTTON_NEGATIVE:
+                onNegative();
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+
+        mTimeout = savedInstanceState.getBoolean(KEY_USER_TIMEOUT);
+
+        if (mTimeout) {
+            onTimeout();
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putBoolean(KEY_USER_TIMEOUT, mTimeout);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        if (s.length() > 0) {
+            mOkButton.setEnabled(true);
+        }
+    }
+
+    private void showAuthDialog() {
+        final AlertController.AlertParams p = mAlertParams;
+
+        p.mIconId = android.R.drawable.ic_dialog_info;
+        p.mTitle = getString(R.string.pbap_session_key_dialog_header);
+
+        p.mPositiveButtonText = getString(android.R.string.ok);
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonText = getString(android.R.string.cancel);
+        p.mNegativeButtonListener = this;
+
+        p.mView = createView();
+
+        setupAlert();
+
+        mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+        mOkButton.setEnabled(false);
+    }
+
+    private View createView() {
+        View view = getLayoutInflater().inflate(R.layout.pbap_auth, null);
+
+        mMessageTextView = (TextView) view.findViewById(R.id.message);
+        mMessageTextView.setText(getString(R.string.pbap_session_key_dialog_title,
+                mDevice.getName()));
+
+        mKeyEditText = (EditText) view.findViewById(R.id.text);
+        mKeyEditText.addTextChangedListener(this);
+        mKeyEditText.setFilters(new InputFilter[] {
+                new InputFilter.LengthFilter(KEY_MAX_LENGTH)
+        });
+
+        return view;
+    }
+
+    private void onTimeout() {
+        mTimeout = true;
+
+        mMessageTextView.setText(getString(R.string.pbap_authentication_timeout_message,
+                mDevice.getName()));
+
+        mKeyEditText.setVisibility(View.GONE);
+        mKeyEditText.clearFocus();
+        mKeyEditText.removeTextChangedListener(PbapAuthActivity.this);
+
+        mOkButton.setEnabled(true);
+
+        mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+
+        mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(TIMEOUT_MSG),
+                TIMEOUT_VALUE_MS);
+    }
+
+    private void onPositive() {
+        if (!mTimeout) {
+            Intent intent = new Intent(ProfileService.PBAP_AUTH_ACTION_RESPONSE);
+            intent.putExtra(ProfileService.PBAP_AUTH_EXTRA_KEY, mKey);
+            sendBroadcast(intent);
+
+            mKeyEditText.removeTextChangedListener(this);
+        }
+
+        mTimeout = false;
+        finish();
+    }
+
+    private void onNegative() {
+        Intent intent = new Intent(ProfileService.PBAP_AUTH_ACTION_CANCEL);
+        sendBroadcast(intent);
+
+        mKeyEditText.removeTextChangedListener(this);
+
+        finish();
+    }
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/util/Logger.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/util/Logger.java
new file mode 100644
index 0000000..26155cc
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/util/Logger.java
@@ -0,0 +1,83 @@
+/*
+ * 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.bttestapp.util;
+
+import android.util.Log;
+
+public class Logger {
+
+    // Android Tag for logging.
+    private static final String TAG = "NexusCarKit";
+
+    private static final boolean DEBUG = true;
+
+    private static final boolean VLOG = DEBUG && true;
+    private static final boolean DLOG = DEBUG && true;
+    private static final boolean ILOG = DEBUG && true;
+    private static final boolean WLOG = DEBUG && true;
+    private static final boolean ELOG = DEBUG && true;
+
+    /**
+     * Constructor.
+     */
+    private Logger() {
+    }
+
+    public static void v(String tag, String msg) {
+        if (VLOG) {
+            Log.v(TAG, "[" + tag + "] " + msg);
+        }
+    }
+
+    public static void d(String tag, String msg) {
+        if (DLOG) {
+            Log.d(TAG, "[" + tag + "] " + msg);
+        }
+    }
+
+    public static void i(String tag, String msg) {
+        if (ILOG) {
+            Log.i(TAG, "[" + tag + "] " + msg);
+        }
+    }
+
+    public static void w(String tag, String msg) {
+        if (WLOG) {
+            Log.w(TAG, "[" + tag + "] " + msg);
+        }
+    }
+
+    public static void e(String tag, String msg) {
+        if (ELOG) {
+            Log.e(TAG, "[" + tag + "] " + msg);
+        }
+    }
+
+}
diff --git a/bttestapp/src/org/codeaurora/bluetooth/bttestapp/util/MonkeyEvent.java b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/util/MonkeyEvent.java
new file mode 100644
index 0000000..a3ee825
--- /dev/null
+++ b/bttestapp/src/org/codeaurora/bluetooth/bttestapp/util/MonkeyEvent.java
@@ -0,0 +1,132 @@
+/*
+ * 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.bttestapp.util;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MonkeyEvent {
+
+    private static final String TAG = "MonkeyEventStream";
+
+    private static final String SEP = "~~";
+
+    private final String mName;
+
+    private final boolean mPass;
+
+    private HashMap<String, String> mParams = null;
+
+    private ArrayList<String> mExtReply = null;
+
+    public MonkeyEvent(String name, boolean pass) {
+        mName = name.toLowerCase();
+        mPass = pass;
+        mParams = new HashMap<String, String>();
+    }
+
+    public MonkeyEvent addReplyParam(String name, int value) {
+        addReplyParam(name, Integer.toString(value));
+        return this;
+    }
+
+    public MonkeyEvent addReplyParam(String name, long value) {
+        addReplyParam(name, Long.toString(value));
+        return this;
+    }
+
+    public MonkeyEvent addReplyParam(String name, String value) {
+        name = name.toLowerCase();
+        mParams.put(name, value);
+        return this;
+    }
+
+    public MonkeyEvent addExtReply(String s) {
+        if (s != null) {
+            if (mExtReply == null) {
+                mExtReply = new ArrayList<String>();
+            }
+
+            mExtReply.add(s);
+        }
+
+        return this;
+    }
+
+    public MonkeyEvent addExtReply(Collection<?> p) {
+        if (p != null && p.size() != 0) {
+            if (mExtReply == null) {
+                mExtReply = new ArrayList<String>();
+            }
+
+            for (Object obj : p.toArray()) {
+                mExtReply.add(obj.toString());
+            }
+        }
+
+        return this;
+    }
+
+    public void send() {
+        StringBuilder sb = new StringBuilder();
+
+        if (mExtReply == null) {
+            sb.append("EVENT");
+        } else {
+            sb.append("BEGINEXTEVENT");
+        }
+
+        sb.append(SEP);
+        sb.append(mName);
+        sb.append(SEP);
+        sb.append(mPass ? 1 : 0);
+
+        for (Map.Entry<String, String> entry : mParams.entrySet()) {
+            sb.append(SEP);
+            sb.append(entry.getKey());
+            sb.append("=");
+            sb.append(entry.getValue());
+        }
+
+        Log.i(TAG, sb.toString());
+
+        if (mExtReply != null) {
+            for (String s : mExtReply) {
+                Log.i(TAG, s);
+            }
+
+            Log.i(TAG, "ENDEXTEVENT");
+        }
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/ftp/BluetoothFtpService.java b/src/org/codeaurora/bluetooth/ftp/BluetoothFtpService.java
index ceec144..2cb8a14 100644
--- a/src/org/codeaurora/bluetooth/ftp/BluetoothFtpService.java
+++ b/src/org/codeaurora/bluetooth/ftp/BluetoothFtpService.java
@@ -216,7 +216,6 @@
 
 
     private BluetoothSocket mConnSocket = null;
-    private static HashSet<BluetoothDevice> trustDevices = new HashSet<BluetoothDevice>();
     private BluetoothDevice mRemoteDevice = null;
 
     private static String sRemoteDeviceName = null;
@@ -313,8 +312,10 @@
             isWaitingAuthorization = false;
 
                 if (intent.getBooleanExtra(BluetoothFtpService.EXTRA_ALWAYS_ALLOWED, false)) {
-                   trustDevices.add(mRemoteDevice);
-                  Log.v(TAG, "setTrust() D: " + mRemoteDevice.getName()+ "ADDED: " + trustDevices.contains(mRemoteDevice));
+                    if(mRemoteDevice != null) {
+                       mRemoteDevice.setTrust(true);
+                       Log.v(TAG, "setTrust() TRUE " + mRemoteDevice.getName());
+                    }
                 }
                 try {
                     if (mConnSocket != null) {
@@ -350,21 +351,6 @@
                    removeTimeoutMsg = false;
                  }
             }
-        } else if ( BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
-
-            if (intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
-               BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                if(device != null)
-                    Log.d(TAG,"device: "+ device.getName());
-                if(mRemoteDevice != null)
-                    Log.d(TAG," Remtedevie: "+mRemoteDevice.getName());
-               if (device != null && trustDevices.contains(device) &&
-                     intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE) == BluetoothDevice.BOND_NONE) {
-                   Log.d(TAG,"BOND_STATE_CHANGED RFRSH trustDevices"+ device.getName());
-                   trustDevices.remove(device);
-               }
-            }
-
         } else {
             removeTimeoutMsg = false;
         }
@@ -644,8 +630,8 @@
                     mSessionStatusHandler.sendMessage(mSessionStatusHandler
                            .obtainMessage(MSG_INTERNAL_OBEX_RFCOMM_SESSION_UP));
                         boolean trust = false;
-                    if (trustDevices != null)
-                       trust = trustDevices.contains(mRemoteDevice);
+                    if (mRemoteDevice != null)
+                       trust = mRemoteDevice.getTrustState();
 
                     if (VERBOSE) Log.v(RTAG, "GetTrustState() = " + trust);
 
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasService.java b/src/org/codeaurora/bluetooth/map/BluetoothMasService.java
index cbdbdba..a9434ef 100755
--- a/src/org/codeaurora/bluetooth/map/BluetoothMasService.java
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasService.java
@@ -182,7 +182,6 @@
 
     BluetoothMns mnsClient;
     private static BluetoothDevice mRemoteDevice = null;
-    private static HashSet<BluetoothDevice> trustDevices = new HashSet<BluetoothDevice>();
 
     private boolean mHasStarted = false;
     private int mStartId = -1;
@@ -389,8 +388,10 @@
                return;
             }
             if (intent.getBooleanExtra(BluetoothMasService.EXTRA_ALWAYS_ALLOWED, false) == true) {
-                   trustDevices.add(mRemoteDevice);
-                  Log.v(TAG, "setTrust() TRUE " + mRemoteDevice.getName());
+                if(mRemoteDevice != null) {
+                   mRemoteDevice.setTrust(true);
+                   Log.v(TAG, "setTrust() TRUE " + mRemoteDevice.getName());
+                }
             }
             Log.v(TAG, "parseIntent 2: mIsEmailEnabled: " + mIsEmailEnabled);
             if(mIsEmailEnabled) {
@@ -405,22 +406,6 @@
             notifyAuthKeyInput(sessionkey);
         } else if (AUTH_CANCELLED_ACTION.equals(action)) {
             notifyAuthCancelled();
-        } else if ( BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
-
-            if (intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
-               BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                if(device != null)
-                    Log.d(TAG,"device: "+ device.getName());
-                if(mRemoteDevice != null)
-                    Log.d(TAG," Remtedevie: "+mRemoteDevice.getName());
-               if (device != null && trustDevices.contains(device) &&
-                     intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE) == BluetoothDevice.BOND_NONE) {
-                   Log.d(TAG,"BOND_STATE_CHANGED REFRESH trustDevices"+ device.getName());
-                   trustDevices.remove(device);
-               }
-            }
-
-            removeTimeoutMsg = false;
         } else {
             removeTimeoutMsg = false;
         }
@@ -957,8 +942,8 @@
                             continue;
                         }
                         boolean trust = false;
-                        if (trustDevices != null)
-                           trust = trustDevices.contains(mRemoteDevice);
+                        if (mRemoteDevice != null)
+                           trust = mRemoteDevice.getTrustState();
                         if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
                         if (mIsRequestBeingNotified) {
                             if (VERBOSE) Log.v(TAG, "Request notification is still on going.");
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));
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapCard.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapCard.java
new file mode 100644
index 0000000..21d9371
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapCard.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntry.EmailData;
+import com.android.vcard.VCardEntry.NameData;
+import com.android.vcard.VCardEntry.PhoneData;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * Entry representation of folder listing
+ */
+public class BluetoothPbapCard {
+
+    public final String handle;
+
+    public final String N;
+    public final String lastName;
+    public final String firstName;
+    public final String middleName;
+    public final String prefix;
+    public final String suffix;
+
+    public BluetoothPbapCard(String handle, String name) {
+        this.handle = handle;
+
+        N = name;
+
+        /*
+         * format is as for vCard N field, so we have up to 5 tokens: LastName;
+         * FirstName; MiddleName; Prefix; Suffix
+         */
+        String[] parsedName = name.split(";", 5);
+
+        lastName = parsedName.length < 1 ? null : parsedName[0];
+        firstName = parsedName.length < 2 ? null : parsedName[1];
+        middleName = parsedName.length < 3 ? null : parsedName[2];
+        prefix = parsedName.length < 4 ? null : parsedName[3];
+        suffix = parsedName.length < 5 ? null : parsedName[4];
+    }
+
+    @Override
+    public String toString() {
+        JSONObject json = new JSONObject();
+
+        try {
+            json.put("handle", handle);
+            json.put("N", N);
+            json.put("lastName", lastName);
+            json.put("firstName", firstName);
+            json.put("middleName", middleName);
+            json.put("prefix", prefix);
+            json.put("suffix", suffix);
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        return json.toString();
+    }
+
+    static public String jsonifyVcardEntry(VCardEntry vcard) {
+        JSONObject json = new JSONObject();
+
+        try {
+            NameData name = vcard.getNameData();
+            json.put("formatted", name.getFormatted());
+            json.put("family", name.getFamily());
+            json.put("given", name.getGiven());
+            json.put("middle", name.getMiddle());
+            json.put("prefix", name.getPrefix());
+            json.put("suffix", name.getSuffix());
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        try {
+            JSONArray jsonPhones = new JSONArray();
+
+            List<PhoneData> phones = vcard.getPhoneList();
+
+            if (phones != null) {
+                for (PhoneData phone : phones) {
+                    JSONObject jsonPhone = new JSONObject();
+                    jsonPhone.put("type", phone.getType());
+                    jsonPhone.put("number", phone.getNumber());
+                    jsonPhone.put("label", phone.getLabel());
+                    jsonPhone.put("is_primary", phone.isPrimary());
+
+                    jsonPhones.put(jsonPhone);
+                }
+
+                json.put("phones", jsonPhones);
+            }
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        try {
+            JSONArray jsonEmails = new JSONArray();
+
+            List<EmailData> emails = vcard.getEmailList();
+
+            if (emails != null) {
+                for (EmailData email : emails) {
+                    JSONObject jsonEmail = new JSONObject();
+                    jsonEmail.put("type", email.getType());
+                    jsonEmail.put("address", email.getAddress());
+                    jsonEmail.put("label", email.getLabel());
+                    jsonEmail.put("is_primary", email.isPrimary());
+
+                    jsonEmails.put(jsonEmail);
+                }
+
+                json.put("emails", jsonEmails);
+            }
+        } catch (JSONException e) {
+            // do nothing
+        }
+
+        return json.toString();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapClient.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapClient.java
new file mode 100644
index 0000000..b6e688b
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapClient.java
@@ -0,0 +1,858 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Public API to control Phone Book Profile (PCE role only).
+ * <p>
+ * This class defines methods that shall be used by application for the
+ * retrieval of phone book objects from remote device.
+ * <p>
+ * How to connect to remote device which is acting in PSE role:
+ * <ul>
+ * <li>Create a <code>BluetoothDevice</code> object which corresponds to remote
+ * device in PSE role;
+ * <li>Create an instance of <code>BluetoothPbapClient</code> class, passing
+ * <code>BluetothDevice</code> object along with a <code>Handler</code> to it;
+ * <li>Use {@link #setPhoneBookFolderRoot}, {@link #setPhoneBookFolderUp} and
+ * {@link #setPhoneBookFolderDown} to navigate in virtual phone book folder
+ * structure
+ * <li>Use {@link #pullPhoneBookSize} or {@link #pullVcardListingSize} to
+ * retrieve the size of selected phone book
+ * <li>Use {@link #pullPhoneBook} to retrieve phone book entries
+ * <li>Use {@link #pullVcardListing} to retrieve list of entries in the phone
+ * book
+ * <li>Use {@link #pullVcardEntry} to pull single entry from the phone book
+ * </ul>
+ * Upon completion of each call above PCE will notify application if operation
+ * completed successfully (along with results) or failed.
+ * <p>
+ * Therefore, application should handle following events in its message queue
+ * handler:
+ * <ul>
+ * <li><code>EVENT_PULL_PHONE_BOOK_SIZE_DONE</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_SIZE_DONE</code>
+ * <li><code>EVENT_PULL_PHONE_BOOK_DONE</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_DONE</code>
+ * <li><code>EVENT_PULL_VCARD_ENTRY_DONE</code>
+ * <li><code>EVENT_SET_PHONE_BOOK_DONE</code>
+ * </ul>
+ * and
+ * <ul>
+ * <li><code>EVENT_PULL_PHONE_BOOK_SIZE_ERROR</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_SIZE_ERROR</code>
+ * <li><code>EVENT_PULL_PHONE_BOOK_ERROR</code>
+ * <li><code>EVENT_PULL_VCARD_LISTING_ERROR</code>
+ * <li><code>EVENT_PULL_VCARD_ENTRY_ERROR</code>
+ * <li><code>EVENT_SET_PHONE_BOOK_ERROR</code>
+ * </ul>
+ * <code>connect</code> and <code>disconnect</code> methods are introduced for
+ * testing purposes. An application does not need to use them as the session
+ * connection and disconnection happens automatically internally.
+ */
+public class BluetoothPbapClient {
+    private static final String TAG = "BluetoothPbapClient";
+
+    /**
+     * Path to local incoming calls history object
+     */
+    public static final String ICH_PATH = "telecom/ich.vcf";
+
+    /**
+     * Path to local outgoing calls history object
+     */
+    public static final String OCH_PATH = "telecom/och.vcf";
+
+    /**
+     * Path to local missed calls history object
+     */
+    public static final String MCH_PATH = "telecom/mch.vcf";
+
+    /**
+     * Path to local combined calls history object
+     */
+    public static final String CCH_PATH = "telecom/cch.vcf";
+
+    /**
+     * Path to local main phone book object
+     */
+    public static final String PB_PATH = "telecom/pb.vcf";
+
+    /**
+     * Path to incoming calls history object stored on the phone's SIM card
+     */
+    public static final String SIM_ICH_PATH = "SIM1/telecom/ich.vcf";
+
+    /**
+     * Path to outgoing calls history object stored on the phone's SIM card
+     */
+    public static final String SIM_OCH_PATH = "SIM1/telecom/och.vcf";
+
+    /**
+     * Path to missed calls history object stored on the phone's SIM card
+     */
+    public static final String SIM_MCH_PATH = "SIM1/telecom/mch.vcf";
+
+    /**
+     * Path to combined calls history object stored on the phone's SIM card
+     */
+    public static final String SIM_CCH_PATH = "SIM1/telecom/cch.vcf";
+
+    /**
+     * Path to main phone book object stored on the phone's SIM card
+     */
+    public static final String SIM_PB_PATH = "SIM1/telecom/pb.vcf";
+
+    /**
+     * Indicates to server that default sorting order shall be used for vCard
+     * listing.
+     */
+    public static final byte ORDER_BY_DEFAULT = -1;
+
+    /**
+     * Indicates to server that indexed sorting order shall be used for vCard
+     * listing.
+     */
+    public static final byte ORDER_BY_INDEXED = 0;
+
+    /**
+     * Indicates to server that alphabetical sorting order shall be used for the
+     * vCard listing.
+     */
+    public static final byte ORDER_BY_ALPHABETICAL = 1;
+
+    /**
+     * Indicates to server that phonetical (based on sound attribute) sorting
+     * order shall be used for the vCard listing.
+     */
+    public static final byte ORDER_BY_PHONETIC = 2;
+
+    /**
+     * Indicates to server that Name attribute of vCard shall be used to carry
+     * out the search operation on
+     */
+    public static final byte SEARCH_ATTR_NAME = 0;
+
+    /**
+     * Indicates to server that Number attribute of vCard shall be used to carry
+     * out the search operation on
+     */
+    public static final byte SEARCH_ATTR_NUMBER = 1;
+
+    /**
+     * Indicates to server that Sound attribute of vCard shall be used to carry
+     * out the search operation
+     */
+    public static final byte SEARCH_ATTR_SOUND = 2;
+
+    /**
+     * VCard format version 2.1
+     */
+    public static final byte VCARD_TYPE_21 = 0;
+
+    /**
+     * VCard format version 3.0
+     */
+    public static final byte VCARD_TYPE_30 = 1;
+
+    /* 64-bit mask used to filter out VCard fields */
+    // TODO: Think of extracting to separate class
+    public static final long VCARD_ATTR_VERSION = 0x000000000000000001;
+    public static final long VCARD_ATTR_FN = 0x000000000000000002;
+    public static final long VCARD_ATTR_N = 0x000000000000000004;
+    public static final long VCARD_ATTR_PHOTO = 0x000000000000000008;
+    public static final long VCARD_ATTR_BDAY = 0x000000000000000010;
+    public static final long VCARD_ATTR_ADDR = 0x000000000000000020;
+    public static final long VCARD_ATTR_LABEL = 0x000000000000000040;
+    public static final long VCARD_ATTR_TEL = 0x000000000000000080;
+    public static final long VCARD_ATTR_EMAIL = 0x000000000000000100;
+    public static final long VCARD_ATTR_MAILER = 0x000000000000000200;
+    public static final long VCARD_ATTR_TZ = 0x000000000000000400;
+    public static final long VCARD_ATTR_GEO = 0x000000000000000800;
+    public static final long VCARD_ATTR_TITLE = 0x000000000000001000;
+    public static final long VCARD_ATTR_ROLE = 0x000000000000002000;
+    public static final long VCARD_ATTR_LOGO = 0x000000000000004000;
+    public static final long VCARD_ATTR_AGENT = 0x000000000000008000;
+    public static final long VCARD_ATTR_ORG = 0x000000000000010000;
+    public static final long VCARD_ATTR_NOTE = 0x000000000000020000;
+    public static final long VCARD_ATTR_REV = 0x000000000000040000;
+    public static final long VCARD_ATTR_SOUND = 0x000000000000080000;
+    public static final long VCARD_ATTR_URL = 0x000000000000100000;
+    public static final long VCARD_ATTR_UID = 0x000000000000200000;
+    public static final long VCARD_ATTR_KEY = 0x000000000000400000;
+    public static final long VCARD_ATTR_NICKNAME = 0x000000000000800000;
+    public static final long VCARD_ATTR_CATEGORIES = 0x000000000001000000;
+    public static final long VCARD_ATTR_PROID = 0x000000000002000000;
+    public static final long VCARD_ATTR_CLASS = 0x000000000004000000;
+    public static final long VCARD_ATTR_SORT_STRING = 0x000000000008000000;
+    public static final long VCARD_ATTR_X_IRMC_CALL_DATETIME =
+            0x000000000010000000;
+
+    /**
+     * Maximal number of entries of the phone book that PCE can handle
+     */
+    public static final short MAX_LIST_COUNT = (short) 0xFFFF;
+
+    /**
+     * Event propagated upon completion of <code>setPhoneBookFolderRoot</code>,
+     * <code>setPhoneBookFolderUp</code> or <code>setPhoneBookFolderDown</code>
+     * request.
+     * <p>
+     * This event indicates that request completed successfully.
+     * @see #setPhoneBookFolderRoot
+     * @see #setPhoneBookFolderUp
+     * @see #setPhoneBookFolderDown
+     */
+    public static final int EVENT_SET_PHONE_BOOK_DONE = 1;
+
+    /**
+     * Event propagated upon completion of <code>pullPhoneBook</code> request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.arg1</code></td>
+     * <td>newMissedCalls parameter (only in case of missed calls history object
+     * request)</td>
+     * </tr>
+     * <tr>
+     * <td><code>msg.obj</code></td>
+     * <td>which is a list of <code>VCardEntry</code> objects</td>
+     * </tr>
+     * </table>
+     * @see #pullPhoneBook
+     */
+    public static final int EVENT_PULL_PHONE_BOOK_DONE = 2;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardListing</code>
+     * request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.arg1</code></td>
+     * <td>newMissedCalls parameter (only in case of missed calls history object
+     * request)</td>
+     * </tr>
+     * <tr>
+     * <td><code>msg.obj</code></td>
+     * <td>which is a list of <code>BluetoothPbapCard</code> objects</td>
+     * </tr>
+     * </table>
+     * @see #pullVcardListing
+     */
+    public static final int EVENT_PULL_VCARD_LISTING_DONE = 3;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardEntry</code> request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.obj</code></td>
+     * <td>vCard as and object of type <code>VCardEntry</code></td>
+     * </tr>
+     * </table>
+     * @see #pullVcardEntry
+     */
+    public static final int EVENT_PULL_VCARD_ENTRY_DONE = 4;
+
+    /**
+     * Event propagated upon completion of <code>pullPhoneBookSize</code>
+     * request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.arg1</code></td>
+     * <td>size of the phone book</td>
+     * </tr>
+     * </table>
+     * @see #pullPhoneBookSize
+     */
+    public static final int EVENT_PULL_PHONE_BOOK_SIZE_DONE = 5;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardListingSize</code>
+     * request.
+     * <p>
+     * This event carry on results of the request.
+     * <p>
+     * The resulting message contains:
+     * <table>
+     * <tr>
+     * <td><code>msg.arg1</code></td>
+     * <td>size of the phone book listing</td>
+     * </tr>
+     * </table>
+     * @see #pullVcardListingSize
+     */
+    public static final int EVENT_PULL_VCARD_LISTING_SIZE_DONE = 6;
+
+    /**
+     * Event propagated upon completion of <code>setPhoneBookFolderRoot</code>,
+     * <code>setPhoneBookFolderUp</code> or <code>setPhoneBookFolderDown</code>
+     * request. This event indicates an error during operation.
+     */
+    public static final int EVENT_SET_PHONE_BOOK_ERROR = 101;
+
+    /**
+     * Event propagated upon completion of <code>pullPhoneBook</code> request.
+     * This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_PHONE_BOOK_ERROR = 102;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardListing</code>
+     * request. This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_VCARD_LISTING_ERROR = 103;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardEntry</code> request.
+     * This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_VCARD_ENTRY_ERROR = 104;
+
+    /**
+     * Event propagated upon completion of <code>pullPhoneBookSize</code>
+     * request. This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_PHONE_BOOK_SIZE_ERROR = 105;
+
+    /**
+     * Event propagated upon completion of <code>pullVcardListingSize</code>
+     * request. This event indicates an error during operation.
+     */
+    public static final int EVENT_PULL_VCARD_LISTING_SIZE_ERROR = 106;
+
+    /**
+     * Event propagated when PCE has been connected to PSE
+     */
+    public static final int EVENT_SESSION_CONNECTED = 201;
+
+    /**
+     * Event propagated when PCE has been disconnected from PSE
+     */
+    public static final int EVENT_SESSION_DISCONNECTED = 202;
+    public static final int EVENT_SESSION_AUTH_REQUESTED = 203;
+    public static final int EVENT_SESSION_AUTH_TIMEOUT = 204;
+
+    public enum ConnectionState {
+        DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING;
+    }
+
+    private final Handler mClientHandler;
+    private final BluetoothPbapSession mSession;
+    private ConnectionState mConnectionState = ConnectionState.DISCONNECTED;
+
+    private SessionHandler mSessionHandler;
+
+    private static class SessionHandler extends Handler {
+
+        private final WeakReference<BluetoothPbapClient> mClient;
+
+        SessionHandler(BluetoothPbapClient client) {
+            mClient = new WeakReference<BluetoothPbapClient>(client);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Log.d(TAG, "handleMessage: what=" + msg.what);
+
+            BluetoothPbapClient client = mClient.get();
+            if (client == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case BluetoothPbapSession.REQUEST_FAILED:
+                {
+                    BluetoothPbapRequest req = (BluetoothPbapRequest) msg.obj;
+
+                    if (req instanceof BluetoothPbapRequestPullPhoneBookSize) {
+                        client.sendToClient(EVENT_PULL_PHONE_BOOK_SIZE_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestPullVcardListingSize) {
+                        client.sendToClient(EVENT_PULL_VCARD_LISTING_SIZE_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestPullPhoneBook) {
+                        client.sendToClient(EVENT_PULL_PHONE_BOOK_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestPullVcardListing) {
+                        client.sendToClient(EVENT_PULL_VCARD_LISTING_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestPullVcardEntry) {
+                        client.sendToClient(EVENT_PULL_VCARD_ENTRY_ERROR);
+                    } else if (req instanceof BluetoothPbapRequestSetPath) {
+                        client.sendToClient(EVENT_SET_PHONE_BOOK_ERROR);
+                    }
+
+                    break;
+                }
+
+                case BluetoothPbapSession.REQUEST_COMPLETED:
+                {
+                    BluetoothPbapRequest req = (BluetoothPbapRequest) msg.obj;
+
+                    if (req instanceof BluetoothPbapRequestPullPhoneBookSize) {
+                        int size = ((BluetoothPbapRequestPullPhoneBookSize) req).getSize();
+                        client.sendToClient(EVENT_PULL_PHONE_BOOK_SIZE_DONE, size);
+
+                    } else if (req instanceof BluetoothPbapRequestPullVcardListingSize) {
+                        int size = ((BluetoothPbapRequestPullVcardListingSize) req).getSize();
+                        client.sendToClient(EVENT_PULL_VCARD_LISTING_SIZE_DONE, size);
+
+                    } else if (req instanceof BluetoothPbapRequestPullPhoneBook) {
+                        BluetoothPbapRequestPullPhoneBook r = (BluetoothPbapRequestPullPhoneBook) req;
+                        client.sendToClient(EVENT_PULL_PHONE_BOOK_DONE, r.getNewMissedCalls(),
+                                r.getList());
+
+                    } else if (req instanceof BluetoothPbapRequestPullVcardListing) {
+                        BluetoothPbapRequestPullVcardListing r = (BluetoothPbapRequestPullVcardListing) req;
+                        client.sendToClient(EVENT_PULL_VCARD_LISTING_DONE, r.getNewMissedCalls(),
+                                r.getList());
+
+                    } else if (req instanceof BluetoothPbapRequestPullVcardEntry) {
+                        BluetoothPbapRequestPullVcardEntry r = (BluetoothPbapRequestPullVcardEntry) req;
+                        client.sendToClient(EVENT_PULL_VCARD_ENTRY_DONE, r.getVcard());
+
+                    } else if (req instanceof BluetoothPbapRequestSetPath) {
+                        client.sendToClient(EVENT_SET_PHONE_BOOK_DONE);
+                    }
+
+                    break;
+                }
+
+                case BluetoothPbapSession.AUTH_REQUESTED:
+                    client.sendToClient(EVENT_SESSION_AUTH_REQUESTED);
+                    break;
+
+                case BluetoothPbapSession.AUTH_TIMEOUT:
+                    client.sendToClient(EVENT_SESSION_AUTH_TIMEOUT);
+                    break;
+
+                /*
+                 * app does not need to know when session is connected since
+                 * OBEX session is managed inside BluetoothPbapSession
+                 * automatically - we add this only so app can visualize PBAP
+                 * connection status in case it wants to
+                 */
+
+                case BluetoothPbapSession.SESSION_CONNECTING:
+                    client.mConnectionState = ConnectionState.CONNECTING;
+                    break;
+
+                case BluetoothPbapSession.SESSION_CONNECTED:
+                    client.mConnectionState = ConnectionState.CONNECTED;
+                    client.sendToClient(EVENT_SESSION_CONNECTED);
+                    break;
+
+                case BluetoothPbapSession.SESSION_DISCONNECTED:
+                    client.mConnectionState = ConnectionState.DISCONNECTED;
+                    client.sendToClient(EVENT_SESSION_DISCONNECTED);
+                    break;
+            }
+        }
+    };
+
+    private void sendToClient(int eventId) {
+        sendToClient(eventId, 0, null);
+    }
+
+    private void sendToClient(int eventId, int param) {
+        sendToClient(eventId, param, null);
+    }
+
+    private void sendToClient(int eventId, Object param) {
+        sendToClient(eventId, 0, param);
+    }
+
+    private void sendToClient(int eventId, int param1, Object param2) {
+        mClientHandler.obtainMessage(eventId, param1, 0, param2).sendToTarget();
+    }
+
+    /**
+     * Constructs PCE object
+     *
+     * @param device BluetoothDevice that corresponds to remote acting in PSE
+     *            role
+     * @param handler the handle that will be used by PCE to notify events and
+     *            results to application
+     * @throws NullPointerException
+     */
+    public BluetoothPbapClient(BluetoothDevice device, Handler handler) {
+        if (device == null) {
+            throw new NullPointerException("BluetothDevice is null");
+        }
+
+        mClientHandler = handler;
+
+        mSessionHandler = new SessionHandler(this);
+
+        mSession = new BluetoothPbapSession(device, mSessionHandler);
+    }
+
+    /**
+     * Starts a pbap session. <pb> This method set up rfcomm session, obex
+     * session and waits for requests to be transfered to PSE.
+     */
+    public void connect() {
+        mSession.start();
+    }
+
+    @Override
+    public void finalize() {
+        if (mSession != null) {
+            mSession.stop();
+        }
+    }
+
+    /**
+     * Stops all the active transactions and disconnects from the server.
+     */
+    public void disconnect() {
+        mSession.stop();
+    }
+
+    /**
+     * Aborts current request, if any
+     */
+    public void abort() {
+        mSession.abort();
+    }
+
+    public ConnectionState getState() {
+        return mConnectionState;
+    }
+
+    /**
+     * Sets current folder to root
+     *
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_SET_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_SET_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean setPhoneBookFolderRoot() {
+        BluetoothPbapRequest req = new BluetoothPbapRequestSetPath(false);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Sets current folder to parent
+     *
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_SET_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_SET_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean setPhoneBookFolderUp() {
+        BluetoothPbapRequest req = new BluetoothPbapRequestSetPath(true);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Sets current folder to selected sub-folder
+     *
+     * @param folder the name of the sub-folder
+     * @return @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_SET_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_SET_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean setPhoneBookFolderDown(String folder) {
+        BluetoothPbapRequest req = new BluetoothPbapRequestSetPath(folder);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Requests for the number of entries in the phone book.
+     *
+     * @param pbName absolute path to the phone book
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_SIZE_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_SIZE_ERROR} in case of failure
+     */
+    public boolean pullPhoneBookSize(String pbName) {
+        BluetoothPbapRequestPullPhoneBookSize req = new BluetoothPbapRequestPullPhoneBookSize(
+                pbName);
+
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Requests for the number of entries in the phone book listing.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_SIZE_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_SIZE_ERROR} in case of failure
+     */
+    public boolean pullVcardListingSize(String folder) {
+        BluetoothPbapRequestPullVcardListingSize req = new BluetoothPbapRequestPullVcardListingSize(
+                folder);
+
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Pulls complete phone book. This method pulls phone book which entries are
+     * of <code>VCARD_TYPE_21</code> type and each single vCard contains minimal
+     * required set of fields and the number of entries in response is not
+     * limited.
+     *
+     * @param pbName absolute path to the phone book
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean pullPhoneBook(String pbName) {
+        return pullPhoneBook(pbName, 0, VCARD_TYPE_21, 0, 0);
+    }
+
+    /**
+     * Pulls complete phone book. This method pulls all entries from the phone
+     * book.
+     *
+     * @param pbName absolute path to the phone book
+     * @param filter bit mask which indicates which fields of the vCard shall be
+     *            included in each entry of the resulting list
+     * @param format vCard format of entries in the resulting list
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean pullPhoneBook(String pbName, long filter, byte format) {
+        return pullPhoneBook(pbName, filter, format, 0, 0);
+    }
+
+    /**
+     * Pulls complete phone book. This method pulls entries from the phone book
+     * limited to the number of <code>maxListCount</code> starting from the
+     * position of <code>listStartOffset</code>.
+     * <p>
+     * The resulting list contains vCard objects in version
+     * <code>VCARD_TYPE_21</code> which in turns contain minimal required set of
+     * vCard fields.
+     *
+     * @param pbName absolute path to the phone book
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean pullPhoneBook(String pbName, int maxListCount, int listStartOffset) {
+        return pullPhoneBook(pbName, 0, VCARD_TYPE_21, maxListCount, listStartOffset);
+    }
+
+    /**
+     * Pulls complete phone book.
+     *
+     * @param pbName absolute path to the phone book
+     * @param filter bit mask which indicates which fields of the vCard hall be
+     *            included in each entry of the resulting list
+     * @param format vCard format of entries in the resulting list
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_PHONE_BOOK_DONE} or
+     *         {@link #EVENT_PULL_PHONE_BOOK_ERROR} in case of failure
+     */
+    public boolean pullPhoneBook(String pbName, long filter, byte format, int maxListCount,
+            int listStartOffset) {
+        BluetoothPbapRequest req = new BluetoothPbapRequestPullPhoneBook(pbName, filter, format,
+                maxListCount, listStartOffset);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Pulls list of entries in the phone book.
+     * <p>
+     * This method pulls the list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder) {
+        return pullVcardListing(folder, ORDER_BY_DEFAULT, SEARCH_ATTR_NAME, null, 0, 0);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param order the sorting order of the resulting list of entries
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, byte order) {
+        return pullVcardListing(folder, order, SEARCH_ATTR_NAME, null, 0, 0);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>. Only entries where
+     * <code>searchAttr</code> attribute of vCard matches <code>searchVal</code>
+     * will be listed.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param searchAttr vCard attribute which shall be used to carry out search
+     *            operation on
+     * @param searchVal text string used by matching routine to match the value
+     *            of the attribute indicated by SearchAttr
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, byte searchAttr, String searchVal) {
+        return pullVcardListing(folder, ORDER_BY_DEFAULT, searchAttr, searchVal, 0, 0);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param order the sorting order of the resulting list of entries
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, byte order, int maxListCount,
+            int listStartOffset) {
+        return pullVcardListing(folder, order, SEARCH_ATTR_NAME, null, maxListCount,
+                listStartOffset);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, int maxListCount, int listStartOffset) {
+        return pullVcardListing(folder, ORDER_BY_DEFAULT, SEARCH_ATTR_NAME, null, maxListCount,
+                listStartOffset);
+    }
+
+    /**
+     * Pulls list of entries in the <code>folder</code>.
+     *
+     * @param folder the name of the folder to be retrieved
+     * @param order the sorting order of the resulting list of entries
+     * @param searchAttr vCard attribute which shall be used to carry out search
+     *            operation on
+     * @param searchVal text string used by matching routine to match the value
+     *            of the attribute indicated by SearchAttr
+     * @param maxListCount limits number of entries in the response
+     * @param listStartOffset offset to the first entry of the list that would
+     *            be returned
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_LISTING_DONE} or
+     *         {@link #EVENT_PULL_VCARD_LISTING_ERROR} in case of failure
+     */
+    public boolean pullVcardListing(String folder, byte order, byte searchAttr,
+            String searchVal, int maxListCount, int listStartOffset) {
+        BluetoothPbapRequest req = new BluetoothPbapRequestPullVcardListing(folder, order,
+                searchAttr, searchVal, maxListCount, listStartOffset);
+        return mSession.makeRequest(req);
+    }
+
+    /**
+     * Pulls single vCard object
+     *
+     * @param handle handle to the vCard which shall be pulled
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_DONE} or
+     * @link #EVENT_PULL_VCARD_ERROR} in case of failure
+     */
+    public boolean pullVcardEntry(String handle) {
+        return pullVcardEntry(handle, (byte) 0, VCARD_TYPE_21);
+    }
+
+    /**
+     * Pulls single vCard object
+     *
+     * @param handle handle to the vCard which shall be pulled
+     * @param filter bit mask of the vCard fields that shall be included in the
+     *            resulting vCard
+     * @param format resulting vCard version
+     * @return <code>true</code> if request has been sent successfully;
+     *         <code>false</code> otherwise; upon completion PCE sends
+     *         {@link #EVENT_PULL_VCARD_DONE}
+     * @link #EVENT_PULL_VCARD_ERROR} in case of failure
+     */
+    public boolean pullVcardEntry(String handle, long filter, byte format) {
+        BluetoothPbapRequest req = new BluetoothPbapRequestPullVcardEntry(handle, filter, format);
+        return mSession.makeRequest(req);
+    }
+
+    public boolean setAuthResponse(String key) {
+        Log.d(TAG, " setAuthResponse key=" + key);
+        return mSession.setAuthResponse(key);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
new file mode 100644
index 0000000..239e675
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.os.Handler;
+import android.util.Log;
+
+import javax.obex.Authenticator;
+import javax.obex.PasswordAuthentication;
+
+class BluetoothPbapObexAuthenticator implements Authenticator {
+
+    private final static String TAG = "BluetoothPbapObexAuthenticator";
+
+    private String mSessionKey;
+
+    private boolean mReplied;
+
+    private final Handler mCallback;
+
+    public BluetoothPbapObexAuthenticator(Handler callback) {
+        mCallback = callback;
+    }
+
+    public synchronized void setReply(String key) {
+        Log.d(TAG, "setReply key=" + key);
+
+        mSessionKey = key;
+        mReplied = true;
+
+        notify();
+    }
+
+    @Override
+    public PasswordAuthentication onAuthenticationChallenge(String description,
+            boolean isUserIdRequired, boolean isFullAccess) {
+        PasswordAuthentication pa = null;
+
+        mReplied = false;
+
+        Log.d(TAG, "onAuthenticationChallenge: sending request");
+        mCallback.obtainMessage(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_REQUEST)
+                .sendToTarget();
+
+        synchronized (this) {
+            while (!mReplied) {
+                try {
+                    Log.v(TAG, "onAuthenticationChallenge: waiting for response");
+                    this.wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Interrupted while waiting for challenge response");
+                }
+            }
+        }
+
+        if (mSessionKey != null && mSessionKey.length() != 0) {
+            Log.v(TAG, "onAuthenticationChallenge: mSessionKey=" + mSessionKey);
+            pa = new PasswordAuthentication(null, mSessionKey.getBytes());
+        } else {
+            Log.v(TAG, "onAuthenticationChallenge: mSessionKey is empty, timeout/cancel occured");
+        }
+
+        return pa;
+    }
+
+    @Override
+    public byte[] onAuthenticationResponse(byte[] userName) {
+        /* required only in case PCE challenges PSE which we don't do now */
+        return null;
+    }
+
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexSession.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexSession.java
new file mode 100644
index 0000000..d7410cf
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexSession.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Motorola, Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ObexTransport;
+import javax.obex.ResponseCodes;
+
+final class BluetoothPbapObexSession {
+    private static final String TAG = "BluetoothPbapObexSession";
+
+    private static final byte[] PBAP_TARGET = new byte[] {
+            0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66,
+            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
+    };
+
+    final static int OBEX_SESSION_CONNECTED = 100;
+    final static int OBEX_SESSION_FAILED = 101;
+    final static int OBEX_SESSION_DISCONNECTED = 102;
+    final static int OBEX_SESSION_REQUEST_COMPLETED = 103;
+    final static int OBEX_SESSION_REQUEST_FAILED = 104;
+    final static int OBEX_SESSION_AUTHENTICATION_REQUEST = 105;
+    final static int OBEX_SESSION_AUTHENTICATION_TIMEOUT = 106;
+
+    private Handler mSessionHandler;
+    private final ObexTransport mTransport;
+    private ObexClientThread mObexClientThread;
+    private BluetoothPbapObexAuthenticator mAuth = null;
+
+    public BluetoothPbapObexSession(ObexTransport transport) {
+        mTransport = transport;
+    }
+
+    public void start(Handler handler) {
+        Log.d(TAG, "start");
+        mSessionHandler = handler;
+
+        mAuth = new BluetoothPbapObexAuthenticator(mSessionHandler);
+
+        mObexClientThread = new ObexClientThread();
+        mObexClientThread.start();
+    }
+
+    public void stop() {
+        Log.d(TAG, "stop");
+
+        if (mObexClientThread != null) {
+            try {
+                mObexClientThread.interrupt();
+                mObexClientThread.join();
+                mObexClientThread = null;
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    public void abort() {
+        Log.d(TAG, "abort");
+
+        if (mObexClientThread != null && mObexClientThread.mRequest != null) {
+            /*
+             * since abort may block until complete GET is processed inside OBEX
+             * session, let's run it in separate thread so it won't block UI
+             */
+            (new Thread() {
+                @Override
+                public void run() {
+                    mObexClientThread.mRequest.abort();
+                }
+            }).run();
+        }
+    }
+
+    public boolean schedule(BluetoothPbapRequest request) {
+        Log.d(TAG, "schedule: " + request.getClass().getSimpleName());
+
+        if (mObexClientThread == null) {
+            Log.e(TAG, "OBEX session not started");
+            return false;
+        }
+
+        return mObexClientThread.schedule(request);
+    }
+
+    public boolean setAuthReply(String key) {
+        Log.d(TAG, "setAuthReply key=" + key);
+
+        if (mAuth == null) {
+            return false;
+        }
+
+        mAuth.setReply(key);
+
+        return true;
+    }
+
+    private class ObexClientThread extends Thread {
+
+        private static final String TAG = "ObexClientThread";
+
+        private ClientSession mClientSession;
+        private BluetoothPbapRequest mRequest;
+
+        private volatile boolean mRunning = true;
+
+        public ObexClientThread() {
+
+            mClientSession = null;
+            mRequest = null;
+        }
+
+        @Override
+        public void run() {
+            super.run();
+
+            if (!connect()) {
+                mSessionHandler.obtainMessage(OBEX_SESSION_FAILED).sendToTarget();
+                return;
+            }
+
+            mSessionHandler.obtainMessage(OBEX_SESSION_CONNECTED).sendToTarget();
+
+            while (mRunning) {
+                synchronized (this) {
+                    try {
+                        if (mRequest == null) {
+                            this.wait();
+                        }
+                    } catch (InterruptedException e) {
+                        mRunning = false;
+                        break;
+                    }
+                }
+
+                if (mRunning && mRequest != null) {
+                    try {
+                        mRequest.execute(mClientSession);
+                    } catch (IOException e) {
+                        // this will "disconnect" for cleanup
+                        mRunning = false;
+                    }
+
+                    if (mRequest.isSuccess()) {
+                        mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_COMPLETED, mRequest)
+                                .sendToTarget();
+                    } else {
+                        mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_FAILED, mRequest)
+                                .sendToTarget();
+                    }
+                }
+
+                mRequest = null;
+            }
+
+            disconnect();
+
+            mSessionHandler.obtainMessage(OBEX_SESSION_DISCONNECTED).sendToTarget();
+        }
+
+        public synchronized boolean schedule(BluetoothPbapRequest request) {
+            Log.d(TAG, "schedule: " + request.getClass().getSimpleName());
+
+            if (mRequest != null) {
+                return false;
+            }
+
+            mRequest = request;
+            notify();
+
+            return true;
+        }
+
+        private boolean connect() {
+            Log.d(TAG, "connect");
+
+            try {
+                mClientSession = new ClientSession(mTransport);
+                mClientSession.setAuthenticator(mAuth);
+            } catch (IOException e) {
+                return false;
+            }
+
+            HeaderSet hs = new HeaderSet();
+            hs.setHeader(HeaderSet.TARGET, PBAP_TARGET);
+
+            try {
+                hs = mClientSession.connect(hs);
+
+                if (hs.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
+                    disconnect();
+                    return false;
+                }
+            } catch (IOException e) {
+                return false;
+            }
+
+            return true;
+        }
+
+        private void disconnect() {
+            Log.d(TAG, "disconnect");
+
+            if (mClientSession != null) {
+                try {
+                    mClientSession.disconnect(null);
+                    mClientSession.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexTransport.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexTransport.java
new file mode 100644
index 0000000..8576636
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapObexTransport.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Motorola, Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.bluetooth.BluetoothSocket;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.obex.ObexTransport;
+
+class BluetoothPbapObexTransport implements ObexTransport {
+
+    private BluetoothSocket mSocket = null;
+
+    public BluetoothPbapObexTransport(BluetoothSocket rfs) {
+        super();
+        mSocket = rfs;
+    }
+
+    @Override
+    public void close() throws IOException {
+        mSocket.close();
+    }
+
+    @Override
+    public DataInputStream openDataInputStream() throws IOException {
+        return new DataInputStream(openInputStream());
+    }
+
+    @Override
+    public DataOutputStream openDataOutputStream() throws IOException {
+        return new DataOutputStream(openOutputStream());
+    }
+
+    @Override
+    public InputStream openInputStream() throws IOException {
+        return mSocket.getInputStream();
+    }
+
+    @Override
+    public OutputStream openOutputStream() throws IOException {
+        return mSocket.getOutputStream();
+    }
+
+    @Override
+    public void connect() throws IOException {
+    }
+
+    @Override
+    public void create() throws IOException {
+    }
+
+    @Override
+    public void disconnect() throws IOException {
+    }
+
+    @Override
+    public void listen() throws IOException {
+    }
+
+    public boolean isConnected() throws IOException {
+        // return true;
+        return mSocket.isConnected();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequest.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequest.java
new file mode 100644
index 0000000..5f73ffc
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.obex.ClientOperation;
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+abstract class BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequest";
+
+    protected static final byte OAP_TAGID_ORDER = 0x01;
+    protected static final byte OAP_TAGID_SEARCH_VALUE = 0x02;
+    protected static final byte OAP_TAGID_SEARCH_ATTRIBUTE = 0x03;
+    protected static final byte OAP_TAGID_MAX_LIST_COUNT = 0x04;
+    protected static final byte OAP_TAGID_LIST_START_OFFSET = 0x05;
+    protected static final byte OAP_TAGID_FILTER = 0x06;
+    protected static final byte OAP_TAGID_FORMAT = 0x07;
+    protected static final byte OAP_TAGID_PHONEBOOK_SIZE = 0x08;
+    protected static final byte OAP_TAGID_NEW_MISSED_CALLS = 0x09;
+
+    protected HeaderSet mHeaderSet;
+
+    protected int mResponseCode;
+
+    private boolean mAborted = false;
+
+    private ClientOperation mOp = null;
+
+    public BluetoothPbapRequest() {
+        mHeaderSet = new HeaderSet();
+    }
+
+    final public boolean isSuccess() {
+        return (mResponseCode == ResponseCodes.OBEX_HTTP_OK);
+    }
+
+    public void execute(ClientSession session) throws IOException {
+        Log.v(TAG, "execute");
+
+        /* in case request is aborted before can be executed */
+        if (mAborted) {
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+            return;
+        }
+
+        try {
+            mOp = (ClientOperation) session.get(mHeaderSet);
+
+            /* make sure final flag for GET is used (PBAP spec 6.2.2) */
+            mOp.setGetFinalFlag(true);
+
+            /*
+             * this will trigger ClientOperation to use non-buffered stream so
+             * we can abort operation
+             */
+            mOp.continueOperation(true, false);
+
+            readResponseHeaders(mOp.getReceivedHeader());
+
+            InputStream is = mOp.openInputStream();
+            readResponse(is);
+            is.close();
+
+            mOp.close();
+
+            mResponseCode = mOp.getResponseCode();
+
+            Log.d(TAG, "mResponseCode=" + mResponseCode);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException occured when processing request", e);
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+
+            throw e;
+        }
+    }
+
+    public void abort() {
+        mAborted = true;
+
+        if (mOp != null) {
+            try {
+                mOp.abort();
+            } catch (IOException e) {
+                Log.e(TAG, "Exception occured when trying to abort", e);
+            }
+        }
+    }
+
+    protected void readResponse(InputStream stream) throws IOException {
+        Log.v(TAG, "readResponse");
+
+        /* nothing here by default */
+    }
+
+    protected void readResponseHeaders(HeaderSet headerset) {
+        Log.v(TAG, "readResponseHeaders");
+
+        /* nothing here by dafault */
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
new file mode 100644
index 0000000..db08189
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullPhoneBook extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullPhoneBook";
+
+    private static final String TYPE = "x-bt/phonebook";
+
+    private BluetoothPbapVcardList mResponse;
+
+    private int mNewMissedCalls = -1;
+
+    private final byte mFormat;
+
+    public BluetoothPbapRequestPullPhoneBook(String pbName, long filter, byte format,
+            int maxListCount, int listStartOffset) {
+
+        if (maxListCount < 0 || maxListCount > 65535) {
+            throw new IllegalArgumentException("maxListCount should be [0..65535]");
+        }
+
+        if (listStartOffset < 0 || listStartOffset > 65535) {
+            throw new IllegalArgumentException("listStartOffset should be [0..65535]");
+        }
+
+        mHeaderSet.setHeader(HeaderSet.NAME, pbName);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        /* make sure format is one of allowed values */
+        if (format != BluetoothPbapClient.VCARD_TYPE_21
+                && format != BluetoothPbapClient.VCARD_TYPE_30) {
+            format = BluetoothPbapClient.VCARD_TYPE_21;
+        }
+
+        if (filter != 0) {
+            oap.add(OAP_TAGID_FILTER, filter);
+        }
+
+        oap.add(OAP_TAGID_FORMAT, format);
+
+        /*
+         * maxListCount is a special case which is handled in
+         * BluetoothPbapRequestPullPhoneBookSize
+         */
+        if (maxListCount > 0) {
+            oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) maxListCount);
+        } else {
+            oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 65535);
+        }
+
+        if (listStartOffset > 0) {
+            oap.add(OAP_TAGID_LIST_START_OFFSET, (short) listStartOffset);
+        }
+
+        oap.addToHeaderSet(mHeaderSet);
+
+        mFormat = format;
+    }
+
+    @Override
+    protected void readResponse(InputStream stream) throws IOException {
+        Log.v(TAG, "readResponse");
+
+        mResponse = new BluetoothPbapVcardList(stream, mFormat);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        Log.v(TAG, "readResponse");
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        if (oap.exists(OAP_TAGID_NEW_MISSED_CALLS)) {
+            mNewMissedCalls = oap.getByte(OAP_TAGID_NEW_MISSED_CALLS);
+        }
+    }
+
+    public ArrayList<VCardEntry> getList() {
+        return mResponse.getList();
+    }
+
+    public int getNewMissedCalls() {
+        return mNewMissedCalls;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
new file mode 100644
index 0000000..8ad2324
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import javax.obex.HeaderSet;
+
+class BluetoothPbapRequestPullPhoneBookSize extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullPhoneBookSize";
+
+    private static final String TYPE = "x-bt/phonebook";
+
+    private int mSize;
+
+    public BluetoothPbapRequestPullPhoneBookSize(String pbName) {
+        mHeaderSet.setHeader(HeaderSet.NAME, pbName);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+        oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0);
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        Log.v(TAG, "readResponseHeaders");
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE);
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardEntry.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardEntry.java
new file mode 100644
index 0000000..1bbe359
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardEntry.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullVcardEntry extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullVcardEntry";
+
+    private static final String TYPE = "x-bt/vcard";
+
+    private BluetoothPbapVcardList mResponse;
+
+    private final byte mFormat;
+
+    public BluetoothPbapRequestPullVcardEntry(String handle, long filter, byte format) {
+        mHeaderSet.setHeader(HeaderSet.NAME, handle);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        /* make sure format is one of allowed values */
+        if (format != BluetoothPbapClient.VCARD_TYPE_21
+                && format != BluetoothPbapClient.VCARD_TYPE_30) {
+            format = BluetoothPbapClient.VCARD_TYPE_21;
+        }
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        if (filter != 0) {
+            oap.add(OAP_TAGID_FILTER, filter);
+        }
+
+        oap.add(OAP_TAGID_FORMAT, format);
+        oap.addToHeaderSet(mHeaderSet);
+
+        mFormat = format;
+    }
+
+    @Override
+    protected void readResponse(InputStream stream) throws IOException {
+        Log.v(TAG, "readResponse");
+
+        mResponse = new BluetoothPbapVcardList(stream, mFormat);
+
+        if (mResponse.getCount() == 0) {
+            throw new IOException("Invalid response received");
+        }
+    }
+
+    public VCardEntry getVcard() {
+        return mResponse.getFirst();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListing.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListing.java
new file mode 100644
index 0000000..a6863c4
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListing.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+import org.codeaurora.bluetooth.pbapclient.BluetoothPbapVcardListing;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.obex.HeaderSet;
+
+final class BluetoothPbapRequestPullVcardListing extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullVcardListing";
+
+    private static final String TYPE = "x-bt/vcard-listing";
+
+    private BluetoothPbapVcardListing mResponse = null;
+
+    private int mNewMissedCalls = -1;
+
+    public BluetoothPbapRequestPullVcardListing(String folder, byte order, byte searchAttr,
+            String searchVal, int maxListCount, int listStartOffset) {
+
+        if (maxListCount < 0 || maxListCount > 65535) {
+            throw new IllegalArgumentException("maxListCount should be [0..65535]");
+        }
+
+        if (listStartOffset < 0 || listStartOffset > 65535) {
+            throw new IllegalArgumentException("listStartOffset should be [0..65535]");
+        }
+
+        if (folder == null) {
+            folder = "";
+        }
+
+        mHeaderSet.setHeader(HeaderSet.NAME, folder);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+
+        if (order >= 0) {
+            oap.add(OAP_TAGID_ORDER, order);
+        }
+
+        if (searchVal != null) {
+            oap.add(OAP_TAGID_SEARCH_ATTRIBUTE, searchAttr);
+            oap.add(OAP_TAGID_SEARCH_VALUE, searchVal);
+        }
+
+        /*
+         * maxListCount is a special case which is handled in
+         * BluetoothPbapRequestPullVcardListingSize
+         */
+        if (maxListCount > 0) {
+            oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) maxListCount);
+        }
+
+        if (listStartOffset > 0) {
+            oap.add(OAP_TAGID_LIST_START_OFFSET, (short) listStartOffset);
+        }
+
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponse(InputStream stream) throws IOException {
+        Log.v(TAG, "readResponse");
+
+        mResponse = new BluetoothPbapVcardListing(stream);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        Log.v(TAG, "readResponseHeaders");
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        if (oap.exists(OAP_TAGID_NEW_MISSED_CALLS)) {
+            mNewMissedCalls = oap.getByte(OAP_TAGID_NEW_MISSED_CALLS);
+        }
+    }
+
+    public ArrayList<BluetoothPbapCard> getList() {
+        return mResponse.getList();
+    }
+
+    public int getNewMissedCalls() {
+        return mNewMissedCalls;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListingSize.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListingSize.java
new file mode 100644
index 0000000..9fb9295
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestPullVcardListingSize.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import org.codeaurora.bluetooth.utils.ObexAppParameters;
+
+import javax.obex.HeaderSet;
+
+class BluetoothPbapRequestPullVcardListingSize extends BluetoothPbapRequest {
+
+    private static final String TAG = "BluetoothPbapRequestPullVcardListingSize";
+
+    private static final String TYPE = "x-bt/vcard-listing";
+
+    private int mSize;
+
+    public BluetoothPbapRequestPullVcardListingSize(String folder) {
+        mHeaderSet.setHeader(HeaderSet.NAME, folder);
+
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+
+        ObexAppParameters oap = new ObexAppParameters();
+        oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0);
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    protected void readResponseHeaders(HeaderSet headerset) {
+        Log.v(TAG, "readResponseHeaders");
+
+        ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset);
+
+        mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE);
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestSetPath.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestSetPath.java
new file mode 100644
index 0000000..ca6d9b5
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapRequestSetPath.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ResponseCodes;
+
+final class BluetoothPbapRequestSetPath extends BluetoothPbapRequest {
+
+    private final static String TAG = "BluetoothPbapRequestSetPath";
+
+    private enum SetPathDir {
+        ROOT, UP, DOWN
+    };
+
+    private SetPathDir mDir;
+
+    public BluetoothPbapRequestSetPath(String name) {
+        mDir = SetPathDir.DOWN;
+        mHeaderSet.setHeader(HeaderSet.NAME, name);
+    }
+
+    public BluetoothPbapRequestSetPath(boolean goUp) {
+        mHeaderSet.setEmptyNameHeader();
+        if (goUp) {
+            mDir = SetPathDir.UP;
+        } else {
+            mDir = SetPathDir.ROOT;
+        }
+    }
+
+    @Override
+    public void execute(ClientSession session) {
+        Log.v(TAG, "execute");
+
+        HeaderSet hs = null;
+
+        try {
+            switch (mDir) {
+                case ROOT:
+                case DOWN:
+                    hs = session.setPath(mHeaderSet, false, false);
+                    break;
+                case UP:
+                    hs = session.setPath(mHeaderSet, true, false);
+                    break;
+            }
+
+            mResponseCode = hs.getResponseCode();
+        } catch (IOException e) {
+            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        }
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapSession.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapSession.java
new file mode 100644
index 0000000..7cd16f1
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapSession.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.UUID;
+
+class BluetoothPbapSession implements Callback {
+    private static final String TAG = "BluetoothPbapSession";
+
+    /* local use only */
+    private static final int RFCOMM_CONNECTED = 1;
+    private static final int RFCOMM_FAILED = 2;
+
+    /* to BluetoothPbapClient */
+    public static final int REQUEST_COMPLETED = 3;
+    public static final int REQUEST_FAILED = 4;
+    public static final int SESSION_CONNECTING = 5;
+    public static final int SESSION_CONNECTED = 6;
+    public static final int SESSION_DISCONNECTED = 7;
+    public static final int AUTH_REQUESTED = 8;
+    public static final int AUTH_TIMEOUT = 9;
+
+    public static final int ACTION_LISTING = 14;
+    public static final int ACTION_VCARD = 15;
+    public static final int ACTION_PHONEBOOK_SIZE = 16;
+
+    private static final String PBAP_UUID =
+            "0000112f-0000-1000-8000-00805f9b34fb";
+
+    private final BluetoothAdapter mAdapter;
+    private final BluetoothDevice mDevice;
+
+    private final Handler mParentHandler;
+
+    private final HandlerThread mHandlerThread;
+    private final Handler mSessionHandler;
+
+    private RfcommConnectThread mConnectThread;
+    private BluetoothPbapObexTransport mTransport;
+
+    private BluetoothPbapObexSession mObexSession;
+
+    private BluetoothPbapRequest mPendingRequest = null;
+
+    public BluetoothPbapSession(BluetoothDevice device, Handler handler) {
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mAdapter == null) {
+            throw new NullPointerException("No Bluetooth adapter in the system");
+        }
+
+        mDevice = device;
+        mParentHandler = handler;
+        mConnectThread = null;
+        mTransport = null;
+        mObexSession = null;
+
+        mHandlerThread = new HandlerThread("PBAP session handler",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        mHandlerThread.start();
+        mSessionHandler = new Handler(mHandlerThread.getLooper(), this);
+    }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        Log.d(TAG, "Handler: msg: " + msg.what);
+
+        switch (msg.what) {
+            case RFCOMM_FAILED:
+                mConnectThread = null;
+
+                mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
+
+                if (mPendingRequest != null) {
+                    mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
+                    mPendingRequest = null;
+                }
+                break;
+
+            case RFCOMM_CONNECTED:
+                mConnectThread = null;
+                mTransport = (BluetoothPbapObexTransport) msg.obj;
+                startObexSession();
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_FAILED:
+                stopObexSession();
+
+                mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
+
+                if (mPendingRequest != null) {
+                    mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
+                    mPendingRequest = null;
+                }
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_CONNECTED:
+                mParentHandler.obtainMessage(SESSION_CONNECTED).sendToTarget();
+
+                if (mPendingRequest != null) {
+                    mObexSession.schedule(mPendingRequest);
+                    mPendingRequest = null;
+                }
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_DISCONNECTED:
+                mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
+                stopRfcomm();
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_COMPLETED:
+                /* send to parent, process there */
+                mParentHandler.obtainMessage(REQUEST_COMPLETED, msg.obj).sendToTarget();
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_FAILED:
+                /* send to parent, process there */
+                mParentHandler.obtainMessage(REQUEST_FAILED, msg.obj).sendToTarget();
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_REQUEST:
+                /* send to parent, process there */
+                mParentHandler.obtainMessage(AUTH_REQUESTED).sendToTarget();
+
+                mSessionHandler
+                        .sendMessageDelayed(
+                                mSessionHandler
+                                        .obtainMessage(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT),
+                                30000);
+                break;
+
+            case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT:
+                /* stop authentication */
+                setAuthResponse(null);
+
+                mParentHandler.obtainMessage(AUTH_TIMEOUT).sendToTarget();
+                break;
+
+            default:
+                return false;
+        }
+
+        return true;
+    }
+
+    public void start() {
+        Log.d(TAG, "start");
+
+        startRfcomm();
+    }
+
+    public void stop() {
+        Log.d(TAG, "Stop");
+
+        stopObexSession();
+        stopRfcomm();
+    }
+
+    public void abort() {
+        Log.d(TAG, "abort");
+
+        /* fail pending request immediately */
+        if (mPendingRequest != null) {
+            mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
+            mPendingRequest = null;
+        }
+
+        if (mObexSession != null) {
+            mObexSession.abort();
+        }
+    }
+
+    public boolean makeRequest(BluetoothPbapRequest request) {
+        Log.v(TAG, "makeRequest: " + request.getClass().getSimpleName());
+
+        if (mPendingRequest != null) {
+            Log.w(TAG, "makeRequest: request already queued, exiting");
+            return false;
+        }
+
+        if (mObexSession == null) {
+            mPendingRequest = request;
+
+            /*
+             * since there is no pending request and no session it's safe to
+             * assume that RFCOMM does not exist either and we should start
+             * connecting it
+             */
+            startRfcomm();
+
+            return true;
+        }
+
+        return mObexSession.schedule(request);
+    }
+
+    public boolean setAuthResponse(String key) {
+        Log.d(TAG, "setAuthResponse key=" + key);
+
+        mSessionHandler
+                .removeMessages(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT);
+
+        /* does not make sense to set auth response when OBEX session is down */
+        if (mObexSession == null) {
+            return false;
+        }
+
+        return mObexSession.setAuthReply(key);
+    }
+
+    private void startRfcomm() {
+        Log.d(TAG, "startRfcomm");
+
+        if (mConnectThread == null && mObexSession == null) {
+            mParentHandler.obtainMessage(SESSION_CONNECTING).sendToTarget();
+
+            mConnectThread = new RfcommConnectThread();
+            mConnectThread.start();
+        }
+
+        /*
+         * don't care if mConnectThread is not null - it means RFCOMM is being
+         * connected anyway
+         */
+    }
+
+    private void stopRfcomm() {
+        Log.d(TAG, "stopRfcomm");
+
+        if (mConnectThread != null) {
+            try {
+                mConnectThread.join();
+            } catch (InterruptedException e) {
+            }
+
+            mConnectThread = null;
+        }
+
+        if (mTransport != null) {
+            try {
+                mTransport.close();
+            } catch (IOException e) {
+            }
+
+            mTransport = null;
+        }
+    }
+
+    private void startObexSession() {
+        Log.d(TAG, "startObexSession");
+
+        mObexSession = new BluetoothPbapObexSession(mTransport);
+        mObexSession.start(mSessionHandler);
+    }
+
+    private void stopObexSession() {
+        Log.d(TAG, "stopObexSession");
+
+        if (mObexSession != null) {
+            mObexSession.stop();
+            mObexSession = null;
+        }
+    }
+
+    private class RfcommConnectThread extends Thread {
+        private static final String TAG = "RfcommConnectThread";
+
+        private BluetoothSocket mSocket;
+
+        public RfcommConnectThread() {
+            super("RfcommConnectThread");
+        }
+
+        @Override
+        public void run() {
+            if (mAdapter.isDiscovering()) {
+                mAdapter.cancelDiscovery();
+            }
+
+            try {
+                mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(PBAP_UUID));
+                mSocket.connect();
+
+                BluetoothPbapObexTransport transport;
+                transport = new BluetoothPbapObexTransport(mSocket);
+
+                mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
+            } catch (IOException e) {
+                closeSocket();
+                mSessionHandler.obtainMessage(RFCOMM_FAILED).sendToTarget();
+            }
+
+        }
+
+        private void closeSocket() {
+            try {
+                if (mSocket != null) {
+                    mSocket.close();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Error when closing socket", e);
+            }
+        }
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardList.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardList.java
new file mode 100644
index 0000000..35118d8
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardList.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardEntryCounter;
+import com.android.vcard.VCardEntryHandler;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+class BluetoothPbapVcardList {
+
+    private final ArrayList<VCardEntry> mCards = new ArrayList<VCardEntry>();
+
+    class CardEntryHandler implements VCardEntryHandler {
+        @Override
+        public void onStart() {
+        }
+
+        @Override
+        public void onEntryCreated(VCardEntry entry) {
+            mCards.add(entry);
+        }
+
+        @Override
+        public void onEnd() {
+        }
+    }
+
+    public BluetoothPbapVcardList(InputStream in, byte format) throws IOException {
+        parse(in, format);
+    }
+
+    private void parse(InputStream in, byte format) throws IOException {
+        VCardParser parser;
+
+        if (format == BluetoothPbapClient.VCARD_TYPE_30) {
+            parser = new VCardParser_V30();
+        } else {
+            parser = new VCardParser_V21();
+        }
+
+        VCardEntryConstructor constructor = new VCardEntryConstructor();
+        VCardEntryCounter counter = new VCardEntryCounter();
+        CardEntryHandler handler = new CardEntryHandler();
+
+        constructor.addEntryHandler(handler);
+
+        parser.addInterpreter(constructor);
+        parser.addInterpreter(counter);
+
+        try {
+            parser.parse(in);
+        } catch (VCardException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public int getCount() {
+        return mCards.size();
+    }
+
+    public ArrayList<VCardEntry> getList() {
+        return mCards;
+    }
+
+    public VCardEntry getFirst() {
+        return mCards.get(0);
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardListing.java b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardListing.java
new file mode 100644
index 0000000..2b633ec
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/BluetoothPbapVcardListing.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.pbapclient;
+
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+class BluetoothPbapVcardListing {
+
+    private static final String TAG = "BluetoothPbapVcardListing";
+
+    ArrayList<BluetoothPbapCard> mCards = new ArrayList<BluetoothPbapCard>();
+
+    public BluetoothPbapVcardListing(InputStream in) throws IOException {
+        parse(in);
+    }
+
+    private void parse(InputStream in) throws IOException {
+        XmlPullParser parser = Xml.newPullParser();
+
+        try {
+            parser.setInput(in, "UTF-8");
+
+            int eventType = parser.getEventType();
+
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+
+                if (eventType == XmlPullParser.START_TAG && parser.getName().equals("card")) {
+                    BluetoothPbapCard card = new BluetoothPbapCard(
+                            parser.getAttributeValue(null, "handle"),
+                            parser.getAttributeValue(null, "name"));
+                    mCards.add(card);
+                }
+
+                eventType = parser.next();
+            }
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "XML parser error when parsing XML", e);
+        }
+    }
+
+    public ArrayList<BluetoothPbapCard> getList() {
+        return mCards;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/utils/BmsgTokenizer.java b/src/org/codeaurora/bluetooth/pbapclient/utils/BmsgTokenizer.java
new file mode 100644
index 0000000..8e1bbe4
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/utils/BmsgTokenizer.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.utils;
+
+import android.util.Log;
+
+import java.text.ParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class BmsgTokenizer {
+
+    private final String mStr;
+
+    private final Matcher mMatcher;
+
+    private int mPos = 0;
+
+    private final int mOffset;
+
+    static public class Property {
+        public final String name;
+        public final String value;
+
+        public Property(String name, String value) {
+            if (name == null || value == null) {
+                throw new IllegalArgumentException();
+            }
+
+            this.name = name;
+            this.value = value;
+
+            Log.v("BMSG >> ", toString());
+        }
+
+        @Override
+        public String toString() {
+            return name + ":" + value;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return ((o instanceof Property) && ((Property) o).name.equals(name) && ((Property) o).value
+                    .equals(value));
+        }
+    };
+
+    public BmsgTokenizer(String str) {
+        this(str, 0);
+    }
+
+    public BmsgTokenizer(String str, int offset) {
+        mStr = str;
+        mOffset = offset;
+        mMatcher = Pattern.compile("(([^:]*):(.*))?\r\n").matcher(str);
+        mPos = mMatcher.regionStart();
+    }
+
+    public Property next(boolean alwaysReturn) throws ParseException {
+        boolean found = false;
+
+        do {
+            mMatcher.region(mPos, mMatcher.regionEnd());
+
+            if (!mMatcher.lookingAt()) {
+                if (alwaysReturn) {
+                    return null;
+                }
+
+                throw new ParseException("Property or empty line expected", pos());
+            }
+
+            mPos = mMatcher.end();
+
+            if (mMatcher.group(1) != null) {
+                found = true;
+            }
+        } while (!found);
+
+        return new Property(mMatcher.group(2), mMatcher.group(3));
+    }
+
+    public Property next() throws ParseException {
+        return next(false);
+    }
+
+    public String remaining() {
+        return mStr.substring(mPos);
+    }
+
+    public int pos() {
+        return mPos + mOffset;
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/utils/ObexAppParameters.java b/src/org/codeaurora/bluetooth/pbapclient/utils/ObexAppParameters.java
new file mode 100644
index 0000000..cfb7667
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/utils/ObexAppParameters.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.bluetooth.utils;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.obex.HeaderSet;
+
+public final class ObexAppParameters {
+
+    private final HashMap<Byte, byte[]> mParams;
+
+    public ObexAppParameters() {
+        mParams = new HashMap<Byte, byte[]>();
+    }
+
+    public ObexAppParameters(byte[] raw) {
+        mParams = new HashMap<Byte, byte[]>();
+
+        if (raw != null) {
+            for (int i = 0; i < raw.length;) {
+                if (raw.length - i < 2) {
+                    break;
+                }
+
+                byte tag = raw[i++];
+                byte len = raw[i++];
+
+                if (raw.length - i - len < 0) {
+                    break;
+                }
+
+                byte[] val = new byte[len];
+
+                System.arraycopy(raw, i, val, 0, len);
+                this.add(tag, val);
+
+                i += len;
+            }
+        }
+    }
+
+    public static ObexAppParameters fromHeaderSet(HeaderSet headerset) {
+        try {
+            byte[] raw = (byte[]) headerset.getHeader(HeaderSet.APPLICATION_PARAMETER);
+            return new ObexAppParameters(raw);
+        } catch (IOException e) {
+            // won't happen
+        }
+
+        return null;
+    }
+
+    public byte[] getHeader() {
+        int length = 0;
+
+        for (Map.Entry<Byte, byte[]> entry : mParams.entrySet()) {
+            length += (entry.getValue().length + 2);
+        }
+
+        byte[] ret = new byte[length];
+
+        int idx = 0;
+        for (Map.Entry<Byte, byte[]> entry : mParams.entrySet()) {
+            length = entry.getValue().length;
+
+            ret[idx++] = entry.getKey();
+            ret[idx++] = (byte) length;
+            System.arraycopy(entry.getValue(), 0, ret, idx, length);
+            idx += length;
+        }
+
+        return ret;
+    }
+
+    public void addToHeaderSet(HeaderSet headerset) {
+        if (mParams.size() > 0) {
+            headerset.setHeader(HeaderSet.APPLICATION_PARAMETER, getHeader());
+        }
+    }
+
+    public boolean exists(byte tag) {
+        return mParams.containsKey(tag);
+    }
+
+    public void add(byte tag, byte val) {
+        byte[] bval = ByteBuffer.allocate(1).put(val).array();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, short val) {
+        byte[] bval = ByteBuffer.allocate(2).putShort(val).array();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, int val) {
+        byte[] bval = ByteBuffer.allocate(4).putInt(val).array();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, long val) {
+        byte[] bval = ByteBuffer.allocate(8).putLong(val).array();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, String val) {
+        byte[] bval = val.getBytes();
+        mParams.put(tag, bval);
+    }
+
+    public void add(byte tag, byte[] bval) {
+        mParams.put(tag, bval);
+    }
+
+    public byte getByte(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        if (bval == null || bval.length < 1) {
+            return 0;
+        }
+
+        return ByteBuffer.wrap(bval).get();
+    }
+
+    public short getShort(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        if (bval == null || bval.length < 2) {
+            return 0;
+        }
+
+        return ByteBuffer.wrap(bval).getShort();
+    }
+
+    public int getInt(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        if (bval == null || bval.length < 4) {
+            return 0;
+        }
+
+        return ByteBuffer.wrap(bval).getInt();
+    }
+
+    public String getString(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        if (bval == null) {
+            return null;
+        }
+
+        return new String(bval);
+    }
+
+    public byte[] getByteArray(byte tag) {
+        byte[] bval = mParams.get(tag);
+
+        return bval;
+    }
+
+    @Override
+    public String toString() {
+        return mParams.toString();
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/pbapclient/utils/ObexTime.java b/src/org/codeaurora/bluetooth/pbapclient/utils/ObexTime.java
new file mode 100644
index 0000000..83c42ad
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/pbapclient/utils/ObexTime.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *        * Redistributions of source code must retain the above copyright
+ *            notice, this list of conditions and the following disclaimer.
+ *        * Redistributions in binary form must reproduce the above copyright
+ *            notice, this list of conditions and the following disclaimer in the
+ *            documentation and/or other materials provided with the distribution.
+ *        * Neither the name of The Linux Foundation nor
+ *            the names of its contributors may be used to endorse or promote
+ *            products derived from this software without specific prior written
+ *            permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.utils;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class ObexTime {
+
+    private Date mDate;
+
+    public ObexTime(String time) {
+        /*
+         * match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset
+         * +/-hhmm
+         */
+        Pattern p = Pattern
+                .compile("(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(([+-])(\\d{2})(\\d{2}))?");
+        Matcher m = p.matcher(time);
+
+        if (m.matches()) {
+
+            /*
+             * matched groups are numberes as follows: YYYY MM DD T HH MM SS +
+             * hh mm ^^^^ ^^ ^^ ^^ ^^ ^^ ^ ^^ ^^ 1 2 3 4 5 6 8 9 10 all groups
+             * are guaranteed to be numeric so conversion will always succeed
+             * (except group 8 which is either + or -)
+             */
+
+            Calendar cal = Calendar.getInstance();
+            cal.set(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) - 1,
+                    Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)),
+                    Integer.parseInt(m.group(5)), Integer.parseInt(m.group(6)));
+
+            /*
+             * if 7th group is matched then we have UTC offset information
+             * included
+             */
+            if (m.group(7) != null) {
+                int ohh = Integer.parseInt(m.group(9));
+                int omm = Integer.parseInt(m.group(10));
+
+                /* time zone offset is specified in miliseconds */
+                int offset = (ohh * 60 + omm) * 60 * 1000;
+
+                if (m.group(8).equals("-")) {
+                    offset = -offset;
+                }
+
+                TimeZone tz = TimeZone.getTimeZone("UTC");
+                tz.setRawOffset(offset);
+
+                cal.setTimeZone(tz);
+            }
+
+            mDate = cal.getTime();
+        }
+    }
+
+    public ObexTime(Date date) {
+        mDate = date;
+    }
+
+    public Date getTime() {
+        return mDate;
+    }
+
+    @Override
+    public String toString() {
+        if (mDate == null) {
+            return null;
+        }
+
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(mDate);
+
+        /* note that months are numbered stating from 0 */
+        return String.format(Locale.US, "%04d%02d%02dT%02d%02d%02d",
+                cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1,
+                cal.get(Calendar.DATE), cal.get(Calendar.HOUR_OF_DAY),
+                cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND));
+    }
+}
diff --git a/src/org/codeaurora/bluetooth/sap/BluetoothSapService.java b/src/org/codeaurora/bluetooth/sap/BluetoothSapService.java
index f95fa39..aef74ec 100644
--- a/src/org/codeaurora/bluetooth/sap/BluetoothSapService.java
+++ b/src/org/codeaurora/bluetooth/sap/BluetoothSapService.java
@@ -270,8 +270,6 @@
 
     private HashMap<BluetoothDevice, BluetoothSapDevice> mSapDevices;
 
-    private static HashSet<BluetoothDevice> trustDevices = new HashSet<BluetoothDevice>();
-
     private IBinder mSapBinder;
 
     private BluetoothAdapter mAdapter;
@@ -484,8 +482,10 @@
             }
 
             if (intent.getBooleanExtra(BluetoothSapService.SAP_EXTRA_ALWAYS_ALLOWED, false) == true) {
-                   trustDevices.add(mRemoteDevice);
-                  Log.v(TAG, "setTrust() TRUE " + mRemoteDevice.getName());
+                  if(mRemoteDevice != null) {
+                     mRemoteDevice.setTrust(true);
+                     Log.v(TAG, "setTrust() TRUE " + mRemoteDevice.getName());
+                  }
             }
             /* start the uplink thread */
             startUplinkThread();
@@ -495,21 +495,6 @@
             sendErrorConnResp();
             closeRfcommSocket();
             startRfcommListenerThread();
-        } else if ( BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
-
-            if (intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
-               BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                if(device != null)
-                    Log.d(TAG,"device: "+ device.getName());
-                if(mRemoteDevice != null)
-                    Log.d(TAG," Remtedevie: "+mRemoteDevice.getName());
-               if (device != null && trustDevices.contains(device) &&
-                     intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE) == BluetoothDevice.BOND_NONE) {
-                   Log.d(TAG,"BOND_STATE_CHANGED REFRESH trustDevices"+ device.getName());
-                   trustDevices.remove(device);
-               }
-            }
-
         } else {
             removeTimeoutMsg = false;
         }
@@ -835,8 +820,8 @@
                         break;
                     }
                     boolean trust = false;
-                    if (trustDevices != null)
-                        trust = trustDevices.contains(mRemoteDevice);
+                    if (mRemoteDevice != null)
+                        trust = mRemoteDevice.getTrustState();
                     if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
 
                     if (trust) {