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 ></string>
+ <string name="map_msg_push_lbl">Push message ></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 ></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"><</string>
+ <string name="map_msg_row_to">></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 "<sup><small>Re</small></sup>Dial" 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">▌▌▌▌▌</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&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) {