Merge "Bluetooth: HID: Add test app for HID Device Role"
diff --git a/hiddtestapp/Android.mk b/hiddtestapp/Android.mk
new file mode 100644
index 0000000..13de015
--- /dev/null
+++ b/hiddtestapp/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+        $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := HiddTestApp
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hiddtestapp/AndroidManifest.xml b/hiddtestapp/AndroidManifest.xml
new file mode 100644
index 0000000..ca32c1c
--- /dev/null
+++ b/hiddtestapp/AndroidManifest.xml
@@ -0,0 +1,60 @@
+<?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 "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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.codeaurora.bluetooth.hiddtestapp"
+    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-sdk
+        android:minSdkVersion="18"
+        android:targetSdkVersion="18" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+
+        <activity
+            android:name=".MainActivity"
+            android:screenOrientation="landscape" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <service android:name=".HidWrapperService" android:exported="false" />
+
+    </application>
+
+</manifest>
diff --git a/hiddtestapp/res/drawable-hdpi/ic_launcher.png b/hiddtestapp/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f83649f
--- /dev/null
+++ b/hiddtestapp/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/hiddtestapp/res/drawable-mdpi/ic_launcher.png b/hiddtestapp/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..8c4edba
--- /dev/null
+++ b/hiddtestapp/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/hiddtestapp/res/drawable-xhdpi/ic_launcher.png b/hiddtestapp/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..b4d5d6c
--- /dev/null
+++ b/hiddtestapp/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/hiddtestapp/res/layout/activity_main.xml b/hiddtestapp/res/layout/activity_main.xml
new file mode 100644
index 0000000..556e650
--- /dev/null
+++ b/hiddtestapp/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+<!-- 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 "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:id="@+id/fragment_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+</LinearLayout>
diff --git a/hiddtestapp/res/layout/keyboard_fragment.xml b/hiddtestapp/res/layout/keyboard_fragment.xml
new file mode 100644
index 0000000..d6244a0
--- /dev/null
+++ b/hiddtestapp/res/layout/keyboard_fragment.xml
@@ -0,0 +1,66 @@
+<!-- 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 "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" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/led_numlock"
+            android:text="@string/led_numlock"
+            android:background="#00000000"
+            style="@style/keyboard_led" />
+
+        <TextView
+            android:id="@+id/led_capslock"
+            android:text="@string/led_capslock"
+            android:background="#00000000"
+            style="@style/keyboard_led" />
+
+        <TextView
+            android:id="@+id/led_scrolllock"
+            android:text="@string/led_scrolllock"
+            android:background="#00000000"
+            style="@style/keyboard_led" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/keyboard"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical" >
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/hiddtestapp/res/layout/mouse_fragment.xml b/hiddtestapp/res/layout/mouse_fragment.xml
new file mode 100644
index 0000000..14972e9
--- /dev/null
+++ b/hiddtestapp/res/layout/mouse_fragment.xml
@@ -0,0 +1,144 @@
+<!-- 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 "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" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="horizontal" >
+
+        <LinearLayout
+            android:id="@+id/speed_control"
+            android:layout_width="96dip"
+            android:layout_height="match_parent"
+            android:orientation="vertical" >
+
+            <ToggleButton
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:textOn="@string/speed_slow"
+                android:textOff="@string/speed_slow"
+                android:tag="1" />
+
+            <ToggleButton
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:textOn="@string/speed_medium"
+                android:textOff="@string/speed_medium"
+                android:tag="3" />
+
+            <ToggleButton
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:textOn="@string/speed_fast"
+                android:textOff="@string/speed_fast"
+                android:tag="5" />
+
+        </LinearLayout>
+
+        <View
+            android:id="@+id/touchpad"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1" />
+
+        <View
+            android:id="@+id/scrollzone"
+            android:layout_width="96dip"
+            android:layout_height="match_parent"
+            android:background="#f0ffffff" />
+
+        <LinearLayout
+            android:id="@+id/scroll_speed_control"
+            android:layout_width="96dip"
+            android:layout_height="match_parent"
+            android:orientation="vertical" >
+
+            <ToggleButton
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:textOn="@string/speed_slow"
+                android:textOff="@string/speed_slow"
+                android:tag="1" />
+
+            <ToggleButton
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:textOn="@string/speed_medium"
+                android:textOff="@string/speed_medium"
+                android:tag="3" />
+
+            <ToggleButton
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:textOn="@string/speed_fast"
+                android:textOff="@string/speed_fast"
+                android:tag="5" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/buttons"
+        android:layout_width="match_parent"
+        android:layout_height="96dip"
+        android:orientation="horizontal" >
+
+        <Button
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:tag="0" />
+
+        <Button
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:tag="2" />
+
+        <Button
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:tag="1" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/hiddtestapp/res/layout/numeric_keyboard.xml b/hiddtestapp/res/layout/numeric_keyboard.xml
new file mode 100644
index 0000000..ed76fcb
--- /dev/null
+++ b/hiddtestapp/res/layout/numeric_keyboard.xml
@@ -0,0 +1,52 @@
+<!-- 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 "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="horizontal" >
+
+    <LinearLayout
+        android:id="@+id/navigation_keyboard"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1.0"
+        android:orientation="horizontal"
+        android:paddingRight="10dp" >
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/numeric_keyboard"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1.0"
+        android:orientation="horizontal"
+        android:paddingLeft="10dp" >
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/hiddtestapp/res/menu/actions.xml b/hiddtestapp/res/menu/actions.xml
new file mode 100644
index 0000000..822c63e
--- /dev/null
+++ b/hiddtestapp/res/menu/actions.xml
@@ -0,0 +1,57 @@
+<!-- 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 "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/action_pluginfo_placeholder"
+        android:title="@string/unplugged"
+        android:enabled="false"
+        android:showAsAction="ifRoom" />
+
+    <item android:id="@+id/action_proto_boot"
+        android:title="@string/item_proto_boot"
+        android:enabled="false"
+        android:showAsAction="ifRoom" />
+
+    <item android:id="@+id/action_proto_report"
+        android:title="@string/item_proto_report"
+        android:enabled="false"
+        android:showAsAction="ifRoom" />
+
+    <item android:id="@+id/action_connect"
+        android:title="@string/item_connect"
+        android:showAsAction="ifRoom" />
+
+    <item android:id="@+id/action_disconnect"
+        android:title="@string/item_disconnect"
+        android:showAsAction="ifRoom" />
+
+    <item android:id="@+id/action_unplug"
+          android:title="@string/item_unplug"
+          android:showAsAction="ifRoom" />
+
+</menu>
diff --git a/hiddtestapp/res/values-v11/styles.xml b/hiddtestapp/res/values-v11/styles.xml
new file mode 100644
index 0000000..a59c1ca
--- /dev/null
+++ b/hiddtestapp/res/values-v11/styles.xml
@@ -0,0 +1,38 @@
+<!-- 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 "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/hiddtestapp/res/values-v14/styles.xml b/hiddtestapp/res/values-v14/styles.xml
new file mode 100644
index 0000000..c5f60cd
--- /dev/null
+++ b/hiddtestapp/res/values-v14/styles.xml
@@ -0,0 +1,39 @@
+<!-- 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 "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/hiddtestapp/res/values/colors.xml b/hiddtestapp/res/values/colors.xml
new file mode 100644
index 0000000..d6e73ba
--- /dev/null
+++ b/hiddtestapp/res/values/colors.xml
@@ -0,0 +1,33 @@
+<!-- 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 "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="led_on">#008000</color>
+    <color name="led_off">#404040</color>
+
+</resources>
diff --git a/hiddtestapp/res/values/strings.xml b/hiddtestapp/res/values/strings.xml
new file mode 100644
index 0000000..9d8e55c
--- /dev/null
+++ b/hiddtestapp/res/values/strings.xml
@@ -0,0 +1,55 @@
+<!-- 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 "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>
+    <string name="app_name">hidd test app</string>
+
+    <string name="item_proto_boot">P=boot</string>
+    <string name="item_proto_report">P=report</string>
+    <string name="item_connect">Connect</string>
+    <string name="item_disconnect">Disconnect</string>
+    <string name="item_unplug">Unplug</string>
+
+    <string name="tab_mouse">Touchpad</string>
+    <string name="tab_keyboard_qwerty">QWERTY keybard</string>
+    <string name="tab_keyboard_numeric">Numeric keyboard</string>
+
+    <string name="led_numlock">Num Lock</string>
+    <string name="led_capslock">Caps Lock</string>
+    <string name="led_scrolllock">Scroll Lock</string>
+
+    <string name="unplug_title">Unplug device</string>
+    <string name="unplug_message">Do you really want to unplug device from host?</string>
+
+    <string name="speed_slow">Slow</string>
+    <string name="speed_medium">Medium</string>
+    <string name="speed_fast">Fast</string>
+
+    <string name="unplugged">unplugged</string>
+    <string name="plugged">VC=%s</string>
+
+</resources>
diff --git a/hiddtestapp/res/values/styles.xml b/hiddtestapp/res/values/styles.xml
new file mode 100644
index 0000000..e1d0acc
--- /dev/null
+++ b/hiddtestapp/res/values/styles.xml
@@ -0,0 +1,42 @@
+<!-- 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 "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>
+
+    <style name="keyboard_led">
+        <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:layout_width">0dp</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:gravity">center</item>
+    </style>
+
+</resources>
diff --git a/hiddtestapp/res/xml/navigation_keyboard.xml b/hiddtestapp/res/xml/navigation_keyboard.xml
new file mode 100644
index 0000000..ea2d1a2
--- /dev/null
+++ b/hiddtestapp/res/xml/navigation_keyboard.xml
@@ -0,0 +1,60 @@
+<?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 "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.
+-->
+<Keyboard>
+    <Layout>
+        <Key keyLabel="Vol Down &#x21E9;" keyCode="0x87"/>
+        <Key keyLabel="Mute" keyCode="0x85"/>
+        <Key keyLabel="Vol Up &#x21E7;" keyCode="0x86"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="Print Screen" keyCode="0x46"/>
+        <Key keyLabel="Scroll Lock" keyCode="0x47"/>
+        <Key keyLabel="Pause" keyCode="0x48"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="Insert" keyCode="0x49"/>
+        <Key keyLabel="Home" keyCode="0x4A"/>
+        <Key keyLabel="Page Up" keyCode="0x4B"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="Delete" keyCode="0x4C"/>
+        <Key keyLabel="End" keyCode="0x4D"/>
+        <Key keyLabel="Page Down" keyCode="0x4E"/>
+    </Layout>
+    <Layout>
+        <Key visible="false"/>
+        <Key keyLabel="&#x2191;" keyCode="0x52"/>
+        <Key visible="false"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="&#x2190;" keyCode="0x50"/>
+        <Key keyLabel="&#x2193;" keyCode="0x51"/>
+        <Key keyLabel="&#x2192;" keyCode="0x4F"/>
+    </Layout>
+</Keyboard>
diff --git a/hiddtestapp/res/xml/numeric_keyboard.xml b/hiddtestapp/res/xml/numeric_keyboard.xml
new file mode 100644
index 0000000..2a6ce76
--- /dev/null
+++ b/hiddtestapp/res/xml/numeric_keyboard.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 "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.
+-->
+<Keyboard>
+    <Layout>
+        <Key keyLabel="Prev\n&#x21E6;" keyCode="0x80"/>
+        <Key keyLabel="Next\n&#x21E8;" keyCode="0x81"/>
+        <Key keyLabel="Stop" keyCode="0x82"/>
+        <Key keyLabel="Play" keyCode="0x83"/>
+        <Key keyLabel="Pause" keyCode="0x84"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="Num\nLock" keyCode="0x53"/>
+        <Key keyLabel="/" keyCode="0x54"/>
+        <Key keyLabel="*" keyCode="0x55"/>
+        <Key keyLabel="-" keyCode="0x56"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="7\nHome" keyCode="0x5F"/>
+        <Key keyLabel="8 &#x2191;" keyCode="0x60"/>
+        <Key keyLabel="9\nPgUp" keyCode="0x61"/>
+        <Key keyLabel="+" keyCode="0x57"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="4\n&#x2190;" keyCode="0x5C"/>
+        <Key keyLabel="5" keyCode="0x5D"/>
+        <Key keyLabel="6\n&#x2192;" keyCode="0x5E"/>
+        <Key visible="false"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="1\nEnd" keyCode="0x59"/>
+        <Key keyLabel="2 &#x2193;" keyCode="0x5A"/>
+        <Key keyLabel="3\nPgDn" keyCode="0x5B"/>
+        <Key visible="false"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="0\nIns" keyCode="0x62" weight="2.0"/>
+        <Key keyLabel=".\nDel" keyCode="0x63"/>
+        <Key keyLabel="Enter" keyCode="0x58"/>
+    </Layout>
+</Keyboard>
diff --git a/hiddtestapp/res/xml/qwerty_keyboard.xml b/hiddtestapp/res/xml/qwerty_keyboard.xml
new file mode 100644
index 0000000..2dddb62
--- /dev/null
+++ b/hiddtestapp/res/xml/qwerty_keyboard.xml
@@ -0,0 +1,117 @@
+<?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 "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.
+-->
+<Keyboard>
+    <Layout>
+        <Key keyLabel="Esc" keyCode="0x29"/>
+        <Key keyLabel="F1" keyCode="0x3A"/>
+        <Key keyLabel="F2" keyCode="0x3B"/>
+        <Key keyLabel="F3" keyCode="0x3C"/>
+        <Key keyLabel="F4" keyCode="0x3D"/>
+        <Key keyLabel="F5" keyCode="0x3E"/>
+        <Key keyLabel="F6" keyCode="0x3F"/>
+        <Key keyLabel="F7" keyCode="0x40"/>
+        <Key keyLabel="F8" keyCode="0x41"/>
+        <Key keyLabel="F9" keyCode="0x42"/>
+        <Key keyLabel="F10" keyCode="0x43"/>
+        <Key keyLabel="F11" keyCode="0x44"/>
+        <Key keyLabel="F12" keyCode="0x45"/>
+        <Key keyLabel="Del" keyCode="0x4C"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="'" shiftLabel="~" keyCode="0x35"/>
+        <Key keyLabel="1" shiftLabel="!" keyCode="0x1E"/>
+        <Key keyLabel="2" shiftLabel="\@" keyCode="0x1F"/>
+        <Key keyLabel="3" shiftLabel="\#" keyCode="0x20"/>
+        <Key keyLabel="4" shiftLabel="$" keyCode="0x21"/>
+        <Key keyLabel="5" shiftLabel="%" keyCode="0x22"/>
+        <Key keyLabel="6" shiftLabel="^" keyCode="0x23"/>
+        <Key keyLabel="7" shiftLabel="&amp;" keyCode="0x24"/>
+        <Key keyLabel="8" shiftLabel="*" keyCode="0x25"/>
+        <Key keyLabel="9" shiftLabel="(" keyCode="0x26"/>
+        <Key keyLabel="0" shiftLabel=")" keyCode="0x27"/>
+        <Key keyLabel="-" shiftLabel="_" keyCode="0x2D"/>
+        <Key keyLabel="=" shiftLabel="+" keyCode="0x2E"/>
+        <Key keyLabel="Backspace &#x2190;" keyCode="0x2A" weight="1.5"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="Tab &#x21B9;" keyCode="0x2B" weight="1.5"/>
+        <Key keyLabel="Q" keyCode="0x14"/>
+        <Key keyLabel="W" keyCode="0x1A"/>
+        <Key keyLabel="E" keyCode="0x08"/>
+        <Key keyLabel="R" keyCode="0x15"/>
+        <Key keyLabel="T" keyCode="0x17"/>
+        <Key keyLabel="Y" keyCode="0x1C"/>
+        <Key keyLabel="U" keyCode="0x18"/>
+        <Key keyLabel="I" keyCode="0x0C"/>
+        <Key keyLabel="O" keyCode="0x12"/>
+        <Key keyLabel="P" keyCode="0x13"/>
+        <Key keyLabel="[" keyCode="0x2F" shiftLabel="{"/>
+        <Key keyLabel="]" keyCode="0x30" shiftLabel="}"/>
+        <Key keyLabel="\\" keyCode="0x31" shiftLabel="|"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="Caps Lock" keyCode="0x39" weight="1.5"/>
+        <Key keyLabel="A" keyCode="0x04"/>
+        <Key keyLabel="S" keyCode="0x16"/>
+        <Key keyLabel="D" keyCode="0x07"/>
+        <Key keyLabel="F" keyCode="0x09"/>
+        <Key keyLabel="G" keyCode="0x0A"/>
+        <Key keyLabel="H" keyCode="0x0B"/>
+        <Key keyLabel="J" keyCode="0x0D"/>
+        <Key keyLabel="K" keyCode="0x0E"/>
+        <Key keyLabel="L" keyCode="0x0F"/>
+        <Key keyLabel=";" keyCode="0x33" shiftLabel=":"/>
+        <Key keyLabel="'" keyCode="0x34" shiftLabel="&quot;"/>
+        <Key keyLabel="Enter &#x21B5;" keyCode="0x28" weight="3.0"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="Shift &#x21E7;" keyCode="0xE1" keyFunc="Shift" weight="1.5"/>
+        <Key keyLabel="Z" keyCode="0x1D"/>
+        <Key keyLabel="X" keyCode="0x1B"/>
+        <Key keyLabel="C" keyCode="0x06"/>
+        <Key keyLabel="V" keyCode="0x19"/>
+        <Key keyLabel="B" keyCode="0x05"/>
+        <Key keyLabel="N" keyCode="0x11"/>
+        <Key keyLabel="M" keyCode="0x10"/>
+        <Key keyLabel="," keyCode="0x36" shiftLabel="&lt;"/>
+        <Key keyLabel="." keyCode="0x37" shiftLabel="&gt;"/>
+        <Key keyLabel="/" keyCode="0x38" shiftLabel="\?"/>
+        <Key keyLabel="Shift &#x21E7;" keyCode="0xE5" keyFunc="Shift" weight="1.5"/>
+    </Layout>
+    <Layout>
+        <Key keyLabel="Ctrl" keyCode="0xE0" weight="1.5"/>
+        <Key keyLabel="Win" keyCode="0xE3"/>
+        <Key keyLabel="Alt" keyCode="0xE2"/>
+        <Key keyLabel=" " keyCode="0x2C" weight="10.0"/>
+        <Key keyLabel="Alt Gr" keyCode="0xE6"/>
+        <Key keyLabel="Win" keyCode="0xE7"/>
+        <Key keyLabel="Menu" keyCode="0x76"/>
+        <Key keyLabel="Ctrl" keyCode="0xE4" weight="1.5"/>
+    </Layout>
+</Keyboard>
diff --git a/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/HidConsts.java b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/HidConsts.java
new file mode 100644
index 0000000..4d5397c
--- /dev/null
+++ b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/HidConsts.java
@@ -0,0 +1,159 @@
+/*
+ * 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.hiddtestapp;
+
+public class HidConsts {
+
+    public final static String NAME = "HID Device Testapp";
+
+    public final static String DESCRIPTION = "";
+
+    public final static String PROVIDER = "Codeaurora";
+
+    /* @formatter:off */
+    public final static byte[] DESCRIPTOR = {
+        (byte) 0x05, (byte) 0x01,                    // USAGE_PAGE (Generic Desktop)
+        (byte) 0x09, (byte) 0x02,                    // USAGE (Mouse)
+        (byte) 0xa1, (byte) 0x01,                    // COLLECTION (Application)
+        (byte) 0x09, (byte) 0x01,                    //   USAGE (Pointer)
+        (byte) 0xa1, (byte) 0x00,                    //   COLLECTION (Physical)
+        (byte) 0x85, (byte) 0x01,                    //     REPORT_ID (1)
+        (byte) 0x05, (byte) 0x09,                    //     USAGE_PAGE (Button)
+        (byte) 0x19, (byte) 0x01,                    //     USAGE_MINIMUM (Button 1)
+        (byte) 0x29, (byte) 0x03,                    //     USAGE_MAXIMUM (Button 3)
+        (byte) 0x15, (byte) 0x00,                    //     LOGICAL_MINIMUM (0)
+        (byte) 0x25, (byte) 0x01,                    //     LOGICAL_MAXIMUM (1)
+        (byte) 0x95, (byte) 0x03,                    //     REPORT_COUNT (3)
+        (byte) 0x75, (byte) 0x01,                    //     REPORT_SIZE (1)
+        (byte) 0x81, (byte) 0x02,                    //     INPUT (Data,Var,Abs)
+        (byte) 0x95, (byte) 0x01,                    //     REPORT_COUNT (1)
+        (byte) 0x75, (byte) 0x05,                    //     REPORT_SIZE (5)
+        (byte) 0x81, (byte) 0x03,                    //     INPUT (Cnst,Var,Abs)
+        (byte) 0x05, (byte) 0x01,                    //     USAGE_PAGE (Generic Desktop)
+        (byte) 0x09, (byte) 0x30,                    //     USAGE (X)
+        (byte) 0x09, (byte) 0x31,                    //     USAGE (Y)
+        (byte) 0x15, (byte) 0x81,                    //     LOGICAL_MINIMUM (-127)
+        (byte) 0x25, (byte) 0x7f,                    //     LOGICAL_MAXIMUM (127)
+        (byte) 0x75, (byte) 0x08,                    //     REPORT_SIZE (8)
+        (byte) 0x95, (byte) 0x02,                    //     REPORT_COUNT (2)
+        (byte) 0x81, (byte) 0x06,                    //     INPUT (Data,Var,Rel)
+        (byte) 0x09, (byte) 0x38,                    //     USAGE (Wheel)
+        (byte) 0x15, (byte) 0x81,                    //     LOGICAL_MINIMUM (-127)
+        (byte) 0x25, (byte) 0x7f,                    //     LOGICAL_MAXIMUM (127)
+        (byte) 0x75, (byte) 0x08,                    //     REPORT_SIZE (8)
+        (byte) 0x95, (byte) 0x01,                    //     REPORT_COUNT (1)
+        (byte) 0x81, (byte) 0x06,                    //     INPUT (Data,Var,Rel)
+        (byte) 0xc0,                                 //   END_COLLECTION
+        (byte) 0xc0,                                 // END_COLLECTION
+
+        // battery strength
+        (byte) 0x05, (byte) 0x0c,
+        (byte) 0x09, (byte) 0x01,
+        (byte) 0xa1, (byte) 0x01,
+        (byte) 0x85, (byte) 0x20,                    //   REPORT_ID (32)
+        (byte) 0x05, (byte) 0x01,
+        (byte) 0x09, (byte) 0x06,
+        (byte) 0xa1, (byte) 0x02,
+        (byte) 0x05, (byte) 0x06,                    // USAGE_PAGE (Generic Device Controls)
+        (byte) 0x09, (byte) 0x20,                    // USAGE (Battery Strength)
+        (byte) 0x15, (byte) 0x00,                    // LOGICAL_MINIMUM (0)
+        (byte) 0x26, (byte) 0xff, (byte) 0x00,      // LOGICAL_MAXIMUM (100)
+        (byte) 0x75, (byte) 0x08,                    // REPORT_SIZE (8)
+        (byte) 0x95, (byte) 0x01,                    // REPORT_COUNT (1)
+        (byte) 0x81, (byte) 0x02,                    // INPUT (Data,Var,Abs)
+        (byte) 0xc0,
+        (byte) 0xc0,
+
+        (byte) 0x05, (byte) 0x01,                    // USAGE_PAGE (Generic Desktop)
+        (byte) 0x09, (byte) 0x06,                    // USAGE (Keyboard)
+        (byte) 0xa1, (byte) 0x01,                    // COLLECTION (Application)
+        (byte) 0x85, (byte) 0x10,                    //   REPORT_ID (16)
+        (byte) 0x05, (byte) 0x07,                    //   USAGE_PAGE (Keyboard)
+        (byte) 0x19, (byte) 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
+        (byte) 0x29, (byte) 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
+        (byte) 0x15, (byte) 0x00,                    //   LOGICAL_MINIMUM (0)
+        (byte) 0x25, (byte) 0x01,                    //   LOGICAL_MAXIMUM (1)
+        (byte) 0x75, (byte) 0x01,                    //   REPORT_SIZE (1)
+        (byte) 0x95, (byte) 0x08,                    //   REPORT_COUNT (8)
+        (byte) 0x81, (byte) 0x02,                    //   INPUT (Data,Var,Abs)
+        (byte) 0x05, (byte) 0x0c,                    //   USAGE_PAGE (Consumer Devices)
+        (byte) 0x15, (byte) 0x00,                    //   LOGICAL_MINIMUM (0)
+        (byte) 0x25, (byte) 0x01,                    //   LOGICAL_MAXIMUM (1)
+        (byte) 0x95, (byte) 0x08,                    //   REPORT_COUNT (8)
+        (byte) 0x75, (byte) 0x01,                    //   REPORT_SIZE (1)
+        (byte) 0x09, (byte) 0xb6,                    //   USAGE (Scan Previous Track)
+        (byte) 0x09, (byte) 0xb5,                    //   USAGE (Scan Next Track)
+        (byte) 0x09, (byte) 0xb7,                    //   USAGE (Stop)
+        (byte) 0x09, (byte) 0xb0,                    //   USAGE (Play)
+        (byte) 0x09, (byte) 0xb1,                    //   USAGE (Pause)
+        (byte) 0x09, (byte) 0xe2,                    //   USAGE (Mute)
+        (byte) 0x09, (byte) 0xe9,                    //   USAGE (Volume Up)
+        (byte) 0x09, (byte) 0xea,                    //   USAGE (Volume Down)
+        (byte) 0x81, (byte) 0x02,                    //   INPUT (Data,Var,Abs)
+        (byte) 0x05, (byte) 0x07,                    //   USAGE_PAGE (Keyboard)
+        (byte) 0x95, (byte) 0x05,                    //   REPORT_COUNT (5)
+        (byte) 0x75, (byte) 0x01,                    //   REPORT_SIZE (1)
+        (byte) 0x85, (byte) 0x10,                    //   REPORT_ID (16)
+        (byte) 0x05, (byte) 0x08,                    //   USAGE_PAGE (LEDs)
+        (byte) 0x19, (byte) 0x01,                    //   USAGE_MINIMUM (Num Lock)
+        (byte) 0x29, (byte) 0x05,                    //   USAGE_MAXIMUM (Kana)
+        (byte) 0x91, (byte) 0x02,                    //   OUTPUT (Data,Var,Abs)
+        (byte) 0x95, (byte) 0x01,                    //   REPORT_COUNT (1)
+        (byte) 0x75, (byte) 0x03,                    //   REPORT_SIZE (3)
+        (byte) 0x91, (byte) 0x03,                    //   OUTPUT (Cnst,Var,Abs)
+        (byte) 0x95, (byte) 0x06,                    //   REPORT_COUNT (6)
+        (byte) 0x75, (byte) 0x08,                    //   REPORT_SIZE (8)
+        (byte) 0x15, (byte) 0x00,                    //   LOGICAL_MINIMUM (0)
+        (byte) 0x25, (byte) 0x65,                    //   LOGICAL_MAXIMUM (101)
+        (byte) 0x05, (byte) 0x07,                    //   USAGE_PAGE (Keyboard)
+        (byte) 0x19, (byte) 0x00,                  //   USAGE_MINIMUM (Reserved (no event indicated))
+        (byte) 0x29, (byte) 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
+        (byte) 0x81, (byte) 0x00,                    //   INPUT (Data,Ary,Abs)
+        (byte) 0xc0                                  // END_COLLECTION
+    };
+    /* @formatter:on */
+
+    public final static byte MOUSE_REPORT_ID = 1;
+
+    public final static byte KEYBOARD_INPUT_REPORT_ID = 16;
+
+    public final static byte KEYBOARD_OUTPUT_REPORT_ID = 16;
+
+    public final static byte BOOT_KEYBOARD_REPORT_ID = 1;
+
+    public final static byte BOOT_MOUSE_REPORT_ID = 2;
+
+    public final static byte BATTERY_REPORT_ID = 32;
+
+    public final static byte KEYBOARD_LED_NUM_LOCK = 0x01;
+
+    public final static byte KEYBOARD_LED_CAPS_LOCK = 0x02;
+
+    public final static byte KEYBOARD_LED_SCROLL_LOCK = 0x04;
+}
diff --git a/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/HidWrapperService.java b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/HidWrapperService.java
new file mode 100644
index 0000000..92ef97c
--- /dev/null
+++ b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/HidWrapperService.java
@@ -0,0 +1,562 @@
+/*
+ * 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.hiddtestapp;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidDevice;
+import android.bluetooth.BluetoothHidDeviceAppConfiguration;
+import android.bluetooth.BluetoothHidDeviceAppQosSettings;
+import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
+import android.bluetooth.BluetoothHidDeviceCallback;
+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.BatteryManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public class HidWrapperService extends Service {
+
+    private final static String TAG = HidWrapperService.class.getSimpleName();
+
+
+    private final static int MESSAGE_REGISTER_APP = 1;
+
+    private final static int MESSAGE_APP_REGISTERED = 2;
+
+    private final static int MESSAGE_DEV_CONNECTED = 3;
+
+    private final static int MESSAGE_INTR_DATA_RECEIVED = 4;
+
+    private final static int MESSAGE_GET_REPORT_RECEIVED = 5;
+
+
+    private final IBinder mBinder = new LocalBinder();
+
+    private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+    private boolean mAppRegistered = false;
+
+    private BluetoothDevice mPluggedDevice;
+
+    private BluetoothHidDevice mHidDevice;
+
+    private byte mProtocol = BluetoothHidDevice.PROTOCOL_REPORT_MODE;
+
+    private BluetoothHidDeviceAppConfiguration mAppConfig;
+
+    private float mBatteryLevel = 0;
+
+    private int mConnectionState = BluetoothHidDevice.STATE_DISCONNECTED;
+
+    private MouseWrapper mMouseWrapper = new MouseWrapper();
+
+    private KeyboardWrapper mKeyboardWrapper = new KeyboardWrapper();
+
+    private BatteryWrapper mBatteryWrapper = new BatteryWrapper();
+
+    private HidEventListener mEventListener;
+
+    private SparseArray<ReportData> mInputReportCache = new SparseArray<ReportData>();
+
+    public interface HidEventListener {
+        public void onApplicationState(boolean registered);
+        public void onPluggedDeviceChanged(BluetoothDevice device);
+        public void onConnectionState(BluetoothDevice device, boolean connected);
+        public void onProtocolModeState(boolean bootMode);
+        public void onKeyboardLedState(boolean numLock, boolean capsLock, boolean scrollLock);
+    };
+
+    private class ReportData {
+        public byte[] data;
+    }
+
+
+    private final ServiceListener mServiceListener = new ServiceListener() {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (profile != BluetoothProfile.HID_DEVICE) return;
+
+            Log.i(TAG, "Profile proxy connected");
+
+            mHidDevice = (BluetoothHidDevice) proxy;
+
+            mHandler.obtainMessage(MESSAGE_REGISTER_APP).sendToTarget();
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (profile != BluetoothProfile.HID_DEVICE) return;
+
+            Log.i(TAG, "Profile proxy disconnected");
+
+            // do not dereference proxy here as we won't be able to close it
+        }
+    };
+
+    private final BluetoothHidDeviceCallback mCallback = new BluetoothHidDeviceCallback() {
+
+        @Override
+        public void onAppStatusChanged(BluetoothDevice pluggedDevice,
+                                            BluetoothHidDeviceAppConfiguration config, boolean registered) {
+            Log.d(TAG, "onAppStatusChanged: pluggedDevice=" +
+                (pluggedDevice == null ? null : pluggedDevice.toString()) + " registered=" + registered);
+
+            mEventListener.onApplicationState(registered);
+
+            if (registered) {
+                mPluggedDevice = pluggedDevice;
+                mAppConfig = config;
+                mHandler.obtainMessage(MESSAGE_APP_REGISTERED).sendToTarget();
+            } else {
+                mPluggedDevice = null;
+                mAppConfig = null;
+            }
+
+            mEventListener.onPluggedDeviceChanged(mPluggedDevice);
+        }
+
+        @Override
+        public void onConnectionStateChanged(BluetoothDevice device, int state) {
+            mConnectionState = state;
+
+            if (state == BluetoothHidDevice.STATE_CONNECTED) {
+                mEventListener.onConnectionState(device, true);
+                mHandler.obtainMessage(MESSAGE_DEV_CONNECTED).sendToTarget();
+
+                if (!device.equals(mPluggedDevice)) {
+                    mPluggedDevice = device;
+                    mEventListener.onPluggedDeviceChanged(mPluggedDevice);
+                }
+            } else {
+                mEventListener.onConnectionState(device, false);
+            }
+        }
+
+        @Override
+        public void onIntrData(byte reportId, byte[] data) {
+            Log.v(TAG, "intr data: reportId=" + reportId + " data=" + Arrays.toString(data));
+
+            mHandler.obtainMessage(MESSAGE_INTR_DATA_RECEIVED, reportId, 0, ByteBuffer.wrap(data))
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onSetProtocol(byte protocol) {
+            Log.d(TAG, "protocol set to " + protocol);
+
+            mProtocol = protocol;
+
+            mEventListener.onProtocolModeState(protocol == BluetoothHidDevice.PROTOCOL_BOOT_MODE);
+        }
+
+        @Override
+        public void onVirtualCableUnplug() {
+            mPluggedDevice = null;
+            mEventListener.onPluggedDeviceChanged(mPluggedDevice);
+        }
+
+        @Override
+        public void onGetReport(byte type, byte id, int bufferSize) {
+            if (type == BluetoothHidDevice.REPORT_TYPE_INPUT) {
+                mHandler.obtainMessage(MESSAGE_GET_REPORT_RECEIVED, id, bufferSize).sendToTarget();
+            }
+        }
+    };
+
+    private final Handler mHandler = new Handler() {
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_REGISTER_APP:
+                    BluetoothHidDeviceAppSdpSettings sdp = new BluetoothHidDeviceAppSdpSettings(
+                            HidConsts.NAME, HidConsts.DESCRIPTION, HidConsts.PROVIDER,
+                            BluetoothHidDevice.SUBCLASS1_COMBO, HidConsts.DESCRIPTOR);
+
+                    BluetoothHidDeviceAppQosSettings inQos = new BluetoothHidDeviceAppQosSettings(
+                        BluetoothHidDeviceAppQosSettings.SERVICE_GUARANTEED, 200, 2, 200,
+                        10000 /* 10 ms */, 10000 /* 10 ms */);
+
+                    BluetoothHidDeviceAppQosSettings outQos = new BluetoothHidDeviceAppQosSettings(
+                        BluetoothHidDeviceAppQosSettings.SERVICE_GUARANTEED, 900, 9, 900,
+                        10000 /* 10 ms */, 10000 /* 10 ms */);
+
+                    boolean result = mHidDevice.registerApp(sdp, inQos, outQos, mCallback);
+                    Log.v(TAG, "registerApp()=" + result);
+                    break;
+
+                case MESSAGE_DEV_CONNECTED:
+                    mBatteryWrapper.update(mBatteryLevel);
+                    break;
+
+                case MESSAGE_INTR_DATA_RECEIVED:
+                    byte reportId = (byte) msg.arg1;
+                    byte[] data = ((ByteBuffer) msg.obj).array();
+
+                    mKeyboardWrapper.parseIncomingReport(reportId, data, mEventListener);
+                    break;
+
+                case MESSAGE_GET_REPORT_RECEIVED:
+                    byte id = (byte) msg.arg1;
+                    int size = msg.arg2;
+
+                    ReportData rd = mInputReportCache.get(id);
+
+                    if (rd != null) {
+                        mHidDevice.replyReport(BluetoothHidDevice.REPORT_TYPE_INPUT, id, rd.data);
+                    }
+                    break;
+            }
+        }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+
+            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                        BluetoothAdapter.ERROR);
+
+                if (state == BluetoothAdapter.STATE_ON) {
+                    registerApp();
+                } else {
+                    unregisterApp();
+                }
+            } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+                int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+                int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
+
+                mBatteryLevel = (float) level / (float) scale;
+
+                mBatteryWrapper.update(mBatteryLevel);
+            }
+        }
+
+    };
+
+    private final class MouseWrapper {
+
+        private byte[] mBuffer = new byte[4];
+
+        private byte getReportId() {
+            if (mProtocol == BluetoothHidDevice.PROTOCOL_BOOT_MODE) {
+                return HidConsts.BOOT_MOUSE_REPORT_ID;
+            } else {
+                return HidConsts.MOUSE_REPORT_ID;
+            }
+        }
+
+        public synchronized void move(byte dx, byte dy) {
+            // leave buttons state unchanged
+            mBuffer[1] = dx;
+            mBuffer[2] = dy;
+
+            byte id = getReportId();
+            storeInputReport(id, mBuffer);
+            mHidDevice.sendReport(id, mBuffer);
+        }
+
+        public synchronized void buttonDown(int which) {
+            mBuffer[0] |= (1 << which);
+            mBuffer[1] = 0;
+            mBuffer[2] = 0;
+
+            byte id = getReportId();
+            storeInputReport(id, mBuffer);
+            mHidDevice.sendReport(id, mBuffer);
+        }
+
+        public synchronized void buttonUp(int which) {
+            mBuffer[0] &= ~(1 << which);
+            mBuffer[1] = 0;
+            mBuffer[2] = 0;
+
+            byte id = getReportId();
+            storeInputReport(id, mBuffer);
+            mHidDevice.sendReport(id, mBuffer);
+        }
+
+        public synchronized void scroll(byte delta) {
+            mBuffer[3] = delta;
+
+            byte id = getReportId();
+            storeInputReport(id, mBuffer);
+            mHidDevice.sendReport(id, mBuffer);
+
+            // reset to 0 after sending so it won't self-repeat in subsequent
+            // reports
+            mBuffer[3] = 0x00;
+        }
+    };
+
+    private final class KeyboardWrapper {
+
+        private final static byte MODIFIER_BASE = (byte) 0xe0;
+        private final static byte MODIFIER_COUNT = 8; /* left ctrl -> right gui */
+
+        private byte[] mBuffer = new byte[8];
+
+        private byte getReportId(int type) {
+            if (mProtocol == BluetoothHidDevice.PROTOCOL_BOOT_MODE) {
+                return HidConsts.BOOT_KEYBOARD_REPORT_ID;
+            } else {
+                if (type == BluetoothHidDevice.REPORT_TYPE_INPUT) {
+                    return HidConsts.KEYBOARD_INPUT_REPORT_ID;
+                } else {
+                    return HidConsts.KEYBOARD_OUTPUT_REPORT_ID;
+                }
+            }
+        }
+
+        public synchronized void keyDown(byte key) {
+            if (key >= MODIFIER_BASE && key <= MODIFIER_BASE + MODIFIER_COUNT) {
+                mBuffer[0] |= (1 << (key - MODIFIER_BASE));
+            } else if ((key & 0x80) != 0) {
+                mBuffer[1] |= (1 << (key & 0x07));
+            } else {
+                for (int i = 2; i < 8; i++) {
+                    if (mBuffer[i] == 0x00) {
+                        mBuffer[i] = key;
+                        break;
+                    }
+                }
+            }
+
+            byte id = getReportId(BluetoothHidDevice.REPORT_TYPE_INPUT);
+            storeInputReport(id, mBuffer);
+            mHidDevice.sendReport(id, mBuffer);
+        }
+
+        public synchronized void keyUp(byte key) {
+            if (key >= MODIFIER_BASE && key <= MODIFIER_BASE + MODIFIER_COUNT) {
+                mBuffer[0] &= ~(1 << (key - MODIFIER_BASE));
+            } else if ((key & 0x80) != 0) {
+                mBuffer[1] &= ~(1 << (key & 0x07));
+            } else {
+                for (int i = 2; i < 8; i++) {
+                    if (mBuffer[i] == key) {
+                        mBuffer[i] = 0x00;
+                        break;
+                    }
+                }
+            }
+
+            byte id = getReportId(BluetoothHidDevice.REPORT_TYPE_INPUT);
+            storeInputReport(id, mBuffer);
+            mHidDevice.sendReport(id, mBuffer);
+        }
+
+        public void parseIncomingReport(byte reportId, byte[] data, HidEventListener callback) {
+            if (reportId != getReportId(BluetoothHidDevice.REPORT_TYPE_OUTPUT) || callback == null)
+                return;
+
+            byte leds = data[0];
+
+            callback.onKeyboardLedState(
+                    (leds & HidConsts.KEYBOARD_LED_NUM_LOCK) != 0,
+                    (leds & HidConsts.KEYBOARD_LED_CAPS_LOCK) != 0,
+                    (leds & HidConsts.KEYBOARD_LED_SCROLL_LOCK) != 0);
+        }
+    };
+
+    private final class BatteryWrapper {
+
+        private byte[] mBuffer = new byte[1];
+
+        public synchronized void update(float level) {
+            if (mConnectionState != BluetoothHidDevice.STATE_CONNECTED) {
+                // battery update will not trigger reconnection
+                return;
+            }
+
+            // battery strength not supported in boot mode
+            if (mProtocol != BluetoothHidDevice.PROTOCOL_REPORT_MODE)
+                return;
+
+            int val = (int) (level * 255);
+
+            mBuffer[0] = (byte) (val & 0xff);
+
+            if (mHidDevice != null) {
+                storeInputReport(HidConsts.BATTERY_REPORT_ID, mBuffer);
+                mHidDevice.sendReport(HidConsts.BATTERY_REPORT_ID, mBuffer);
+            }
+        }
+    };
+
+    public class LocalBinder extends Binder {
+        HidWrapperService getService() {
+            return HidWrapperService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onCreate() {
+        Log.v(TAG, "onCreate()");
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        registerReceiver(mReceiver, filter);
+
+        registerApp();
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.v(TAG, "onDestroy()");
+
+        unregisterApp();
+
+        unregisterReceiver(mReceiver);
+    }
+
+    private void registerApp() {
+        Log.v(TAG, "registerApp(): mAppRegistered=" + mAppRegistered);
+
+        if (mAppRegistered || !mAdapter.isEnabled())
+            return;
+
+        if (!mAdapter.getProfileProxy(getApplicationContext(), mServiceListener,
+                BluetoothProfile.HID_DEVICE)) {
+            Log.w(TAG, "Cannot obtain profile proxy");
+            return;
+        }
+
+        mAppRegistered = true;
+    }
+
+    private void unregisterApp() {
+        Log.v(TAG, "unregisterApp(): mAppRegistered=" + mAppRegistered);
+
+        if (!mAppRegistered)
+            return;
+
+        if (mHidDevice != null) {
+            if (mAppConfig != null) {
+                mHidDevice.unregisterApp(mAppConfig);
+            }
+            mAdapter.closeProfileProxy(BluetoothProfile.HID_DEVICE, mHidDevice);
+        }
+
+        mAppRegistered = false;
+    }
+
+    public void setEventListener(HidEventListener listener) {
+        mEventListener = listener;
+    }
+
+    public void mouseMove(int dx, int dy) {
+        if (mHidDevice == null) return;
+        Log.i(TAG, "touch event received");
+        mMouseWrapper.move((byte) dx, (byte) dy);
+    }
+
+    public void mouseButtonDown(int which) {
+        if (mHidDevice == null) return;
+        Log.i(TAG, "touch event received");
+        mMouseWrapper.buttonDown(which);
+    }
+
+    public void mouseButtonUp(int which) {
+        if (mHidDevice == null) return;
+        Log.i(TAG, "touch event received");
+        mMouseWrapper.buttonUp(which);
+    }
+
+    public void mouseScroll(byte delta) {
+        if (mHidDevice == null) return;
+        Log.i(TAG, "touch event received");
+        mMouseWrapper.scroll(delta);
+    }
+
+    public void keyboardKeyDown(byte key) {
+        if (mHidDevice == null) return;
+        Log.i(TAG, "touch event received");
+        mKeyboardWrapper.keyDown(key);
+    }
+
+    public void keyboardKeyUp(byte key) {
+        if (mHidDevice == null) return;
+        Log.i(TAG, "touch event received");
+        mKeyboardWrapper.keyUp(key);
+    }
+
+    public void connect() {
+        if (mHidDevice == null) return;
+        mHidDevice.connect();
+    }
+
+    public void disconnect() {
+        if (mHidDevice == null) return;
+        mHidDevice.disconnect();
+    }
+
+    public void unplug() {
+        if (mHidDevice == null) return;
+        mHidDevice.unplug();
+    }
+
+    public BluetoothDevice getPluggedDevice() {
+        return mPluggedDevice;
+    }
+
+    private void storeInputReport(byte reportId, byte[] data) {
+        ReportData rd = mInputReportCache.get(reportId);
+
+        if (rd == null) {
+            rd = new ReportData();
+            mInputReportCache.put(reportId, rd);
+        }
+
+        rd.data = data.clone();
+    }
+}
diff --git a/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/Key.java b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/Key.java
new file mode 100644
index 0000000..a2eb2f4
--- /dev/null
+++ b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/Key.java
@@ -0,0 +1,116 @@
+/*
+ * 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.hiddtestapp;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.Button;
+
+public class Key extends Button {
+
+    private final KeyAttributes mKeyAttributes;
+    private KeyListener mKeyListener;
+
+    public static class KeyAttributes {
+        public String keyFunction;
+        public String mainLabel;
+        public String shiftLabel;
+        public byte keyCode;
+    }
+
+    public interface KeyListener {
+        public void onKeyDown(String keyFunction, byte keyCode);
+
+        public void onKeyUp(String keyFunction, byte keyCode);
+    }
+
+    public Key(Context context, KeyAttributes keyAttributes) {
+        this(context, keyAttributes, null);
+        setDefaultConfiguration();
+    }
+
+    public Key(Context context, KeyAttributes keyAttributes, AttributeSet attrs) {
+        this(context, keyAttributes, attrs, android.R.attr.buttonStyleSmall);
+    }
+
+    public Key(Context context, KeyAttributes keyAttributes, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mKeyAttributes = keyAttributes;
+        setText(mKeyAttributes.mainLabel);
+    }
+
+    private void setDefaultConfiguration() {
+        setPadding(1, 1, 1, 1);
+    }
+
+    public void setShiftState(boolean shiftOn) {
+        String label = null;
+        if (shiftOn) {
+            label = mKeyAttributes.shiftLabel;
+        }
+        else {
+            label = mKeyAttributes.mainLabel;
+        }
+
+        if (label != null && label.length() > 0) {
+            setText(label);
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                dispatchOnKeyDown();
+                break;
+            case MotionEvent.ACTION_UP:
+                dispatchOnKeyUp();
+                break;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    private void dispatchOnKeyDown() {
+        if (mKeyListener != null) {
+            mKeyListener.onKeyDown(mKeyAttributes.keyFunction, mKeyAttributes.keyCode);
+        }
+    }
+
+    private void dispatchOnKeyUp() {
+        if (mKeyListener != null) {
+            mKeyListener.onKeyUp(mKeyAttributes.keyFunction, mKeyAttributes.keyCode);
+        }
+    }
+
+    public void setKeyListener(KeyListener keyListener) {
+        mKeyListener = keyListener;
+    }
+}
diff --git a/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/Keyboard.java b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/Keyboard.java
new file mode 100644
index 0000000..ffdcc68
--- /dev/null
+++ b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/Keyboard.java
@@ -0,0 +1,200 @@
+/*
+ * 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.hiddtestapp;
+
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+public class Keyboard extends LinearLayout implements Key.KeyListener {
+
+    private static final String XML_TAG_LAYOUT = "Layout";
+    private static final String XML_TAG_KEY = "Key";
+    private int mShiftCount = 0;
+    private KeyboardListener mKeyboardListener;
+    private final List<Key> mKeysWithShiftLabel = new ArrayList<Key>();
+
+    public interface KeyboardListener {
+        public void onKeyDown(byte keyCode);
+
+        public void onKeyUp(byte keyCode);
+    }
+
+    public Keyboard(Context context, int xmlResourceID) {
+        super(context);
+
+        setDefaultLayoutConfiguration();
+        loadFromXmlResource(context, xmlResourceID);
+    }
+
+    public Keyboard(Context context, AttributeSet attrs, int xmlResourceID) {
+        super(context, attrs);
+
+        setDefaultLayoutConfiguration();
+        loadFromXmlResource(context, xmlResourceID);
+    }
+
+    public Keyboard(Context context, AttributeSet attrs, int defStyle, int xmlResourceID) {
+        super(context, attrs, defStyle);
+
+        setDefaultLayoutConfiguration();
+        loadFromXmlResource(context, xmlResourceID);
+    }
+
+    private void setDefaultLayoutConfiguration() {
+        setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        setOrientation(LinearLayout.VERTICAL);
+    }
+
+    private void loadFromXmlResource(Context context, int xmlResourceID) {
+        try {
+            XmlResourceParser xmlParser = context.getResources().getXml(xmlResourceID);
+
+            while (xmlParser.getEventType() != XmlResourceParser.END_DOCUMENT) {
+                if (xmlParser.getEventType() == XmlResourceParser.START_TAG
+                        && xmlParser.getName().equals(XML_TAG_LAYOUT)) {
+                    addView(parseKeyLayout(context, xmlParser));
+                }
+                xmlParser.next();
+            }
+            xmlParser.close();
+        } catch (XmlPullParserException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private LinearLayout parseKeyLayout(Context context, XmlResourceParser xmlParser)
+            throws XmlPullParserException, IOException {
+        LinearLayout linearLayout = new LinearLayout(context);
+        linearLayout.setLayoutParams(new LayoutParams(
+                xmlParser.getAttributeIntValue(null, "width", LayoutParams.MATCH_PARENT),
+                xmlParser.getAttributeIntValue(null, "height", 0),
+                xmlParser.getAttributeFloatValue(null, "weight", 1.0f)));
+        linearLayout.setOrientation(xmlParser.getAttributeIntValue(null, "orientation",
+                LinearLayout.HORIZONTAL));
+
+        String tag;
+        do {
+            xmlParser.next();
+            tag = xmlParser.getName();
+
+            if (xmlParser.getEventType() == XmlResourceParser.START_TAG) {
+                if (tag.equals(XML_TAG_LAYOUT)) {
+                    linearLayout.addView(parseKeyLayout(context, xmlParser));
+                } else if (tag.equals(XML_TAG_KEY)) {
+                    Key.KeyAttributes attrs = new Key.KeyAttributes();
+                    attrs.keyFunction = getStringAttributeValue(xmlParser, "keyFunc", "");
+                    attrs.mainLabel = getStringAttributeValue(xmlParser, "keyLabel", "");
+                    attrs.shiftLabel = getStringAttributeValue(xmlParser, "shiftLabel", "");
+                    attrs.keyCode = (byte) xmlParser.getAttributeIntValue(null, "keyCode", 0);
+
+                    Key key = new Key(context, attrs);
+                    key.setLayoutParams(new LayoutParams(
+                            xmlParser.getAttributeIntValue(null, "width", 0),
+                            xmlParser.getAttributeIntValue(null, "height",
+                                    LayoutParams.MATCH_PARENT),
+                            xmlParser.getAttributeFloatValue(null, "weight", 1)));
+                    key.setVisibility(xmlParser.getAttributeBooleanValue(null, "visible", true) ?
+                        VISIBLE : INVISIBLE);
+                    key.setKeyListener(this);
+
+                    if (attrs.shiftLabel != null & attrs.shiftLabel.length() > 0) {
+                        mKeysWithShiftLabel.add(key);
+                    }
+
+                    linearLayout.addView(key);
+                }
+            }
+        } while (xmlParser.getEventType() != XmlResourceParser.END_TAG
+                || !tag.equals(XML_TAG_LAYOUT));
+
+        return linearLayout;
+    }
+
+    private String getStringAttributeValue(XmlResourceParser xmlParser, String attributeName,
+            String defaultValue) {
+        String value = xmlParser.getAttributeValue(null, attributeName);
+        if (value != null)
+            return value;
+        return defaultValue;
+    }
+
+    private void updateShiftlabel(boolean shiftOn) {
+        for (Key key : mKeysWithShiftLabel) {
+            key.setShiftState(shiftOn);
+        }
+    }
+
+    private void dispatchOnKeyDown(byte keyCode) {
+        if (mKeyboardListener != null) {
+            mKeyboardListener.onKeyDown(keyCode);
+        }
+    }
+
+    private void dispatchOnKeyUp(byte keyCode) {
+        if (mKeyboardListener != null) {
+            mKeyboardListener.onKeyUp(keyCode);
+        }
+    }
+
+    public void setKeyboardListener(KeyboardListener keyboardListener) {
+        mKeyboardListener = keyboardListener;
+    }
+
+    @Override
+    public void onKeyDown(String keyFunction, byte keyCode) {
+        dispatchOnKeyDown(keyCode);
+        if (keyFunction.toLowerCase().contains("shift")) {
+            if (mShiftCount == 0) {
+                updateShiftlabel(true);
+            }
+            mShiftCount++;
+        }
+    }
+
+    @Override
+    public void onKeyUp(String keyFunction, byte keyCode) {
+        dispatchOnKeyUp(keyCode);
+        if (mShiftCount > 0 && keyFunction.toLowerCase().contains("shift")) {
+            mShiftCount--;
+            if (mShiftCount == 0) {
+                updateShiftlabel(false);
+            }
+        }
+    }
+}
diff --git a/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/KeyboardFragment.java b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/KeyboardFragment.java
new file mode 100644
index 0000000..0ddd2a1
--- /dev/null
+++ b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/KeyboardFragment.java
@@ -0,0 +1,146 @@
+/*
+ * 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.hiddtestapp;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.LinearLayout;
+
+public class KeyboardFragment extends Fragment {
+
+    public final static int KEYBOARD_TYPE_QWERTY = 0;
+    public final static int KEYBOARD_TYPE_NAVIGATION_AND_NUMERIC = 1;
+
+    private MainActivity mActivity;
+
+    public KeyboardFragment() {
+    }
+
+    private int getType() {
+        return getArguments().getInt("param");
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                                            Bundle savedInstanceState) {
+        View view = null;
+        switch (getType()) {
+            case KEYBOARD_TYPE_NAVIGATION_AND_NUMERIC:
+                view = createNavigationAndNumericKeyboard(getActivity());
+                break;
+            case KEYBOARD_TYPE_QWERTY:
+            default:
+                view = creatQwertyKeyboard(getActivity());
+                break;
+        }
+
+        LinearLayout ll = (LinearLayout) inflater.inflate(R.layout.keyboard_fragment, container,
+                false);
+        ViewGroup keyboard = (ViewGroup) ll.findViewById(R.id.keyboard);
+        keyboard.addView(view);
+
+        return ll;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mActivity = (MainActivity) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(this.getClass().getSimpleName()
+                    + " can be only attached to " + MainActivity.class.getSimpleName());
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        mActivity.queryLeds(this);
+    }
+
+    public void updateLeds(boolean numLock, boolean capsLock, boolean scrollLock) {
+        if (getView() != null) {
+            updateLed(R.id.led_numlock, numLock);
+            updateLed(R.id.led_capslock, capsLock);
+            updateLed(R.id.led_scrolllock, scrollLock);
+        }
+    }
+
+    private void updateLed(int resId, boolean active) {
+        TextView led = (TextView) getView().findViewById(resId);
+
+        if (led == null)
+            return;
+
+        led.setBackgroundColor(getResources().getColor(active ? R.color.led_on : R.color.led_off));
+    }
+
+    private Keyboard createKeyboard(Context context, int xmlResourceID) {
+
+        Keyboard keyboard = new Keyboard(context, xmlResourceID);
+        keyboard.setKeyboardListener(new Keyboard.KeyboardListener() {
+            @Override
+            public void onKeyUp(byte keyCode) {
+                mActivity.getHidWrapper().keyboardKeyUp(keyCode);
+            }
+
+            @Override
+            public void onKeyDown(byte keyCode) {
+                mActivity.getHidWrapper().keyboardKeyDown(keyCode);
+            }
+        });
+        return keyboard;
+    }
+
+    private View creatQwertyKeyboard(Context context) {
+        return createKeyboard(context, R.xml.qwerty_keyboard);
+    }
+
+    private View createNavigationAndNumericKeyboard(Context context) {
+        ViewGroup view = (ViewGroup) View.inflate(context, R.layout.numeric_keyboard, null);
+        ViewGroup child;
+
+        child = (ViewGroup) view.findViewById(R.id.navigation_keyboard);
+        child.addView(createKeyboard(context, R.xml.navigation_keyboard));
+
+        child = (ViewGroup) view.findViewById(R.id.numeric_keyboard);
+        child.addView(createKeyboard(context, R.xml.numeric_keyboard));
+
+        return view;
+    }
+}
diff --git a/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/MainActivity.java b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/MainActivity.java
new file mode 100644
index 0000000..8774ad2
--- /dev/null
+++ b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/MainActivity.java
@@ -0,0 +1,300 @@
+/*
+ * 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.hiddtestapp;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+public class MainActivity extends Activity implements HidWrapperService.HidEventListener {
+
+    private final String TAG = MainActivity.class.getSimpleName();
+
+    private HidWrapperService mHidWrapper;
+
+    private boolean mAppReady = false;
+
+    private boolean mConnected = false;
+
+    private boolean mBootMode = false;
+
+    private boolean mNumLockLed = false;
+
+    private boolean mCapsLockLed = false;
+
+    private boolean mScrollLockLed = false;
+
+    public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
+
+        private Fragment mFragment;
+
+        private final Activity mActivity;
+
+        private final String mTag;
+
+        private final Class<T> mClass;
+
+        private final int mParam;
+
+        public TabListener(Activity activity, String tag, Class<T> clz) {
+            this(activity, tag, clz, 0);
+        }
+
+        public TabListener(Activity activity, String tag, Class<T> clz, int param) {
+            mActivity = activity;
+            mTag = tag;
+            mClass = clz;
+            mParam = param;
+        }
+
+        @Override
+        public void onTabSelected(Tab tab, FragmentTransaction ft) {
+            if (mFragment == null) {
+                Bundle args = new Bundle();
+                args.putInt("param", mParam);
+
+                mFragment = Fragment.instantiate(mActivity, mClass.getName(), args);
+                ft.add(R.id.fragment_container, mFragment, mTag);
+            } else {
+                ft.attach(mFragment);
+            }
+        }
+
+        @Override
+        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+            if (mFragment != null) {
+                ft.detach(mFragment);
+            }
+        }
+
+        @Override
+        public void onTabReselected(Tab tab, FragmentTransaction ft) {
+            // do nothing
+        }
+    }
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mHidWrapper = ((HidWrapperService.LocalBinder) service).getService();
+            mHidWrapper.setEventListener(MainActivity.this);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mHidWrapper = null;
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Log.v(TAG, "onCreate");
+
+        setContentView(R.layout.activity_main);
+
+        ActionBar ab = getActionBar();
+        ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+        Tab tab;
+
+        tab = ab.newTab();
+        tab.setText(R.string.tab_mouse);
+        tab.setTabListener(new TabListener<MouseFragment>(this, "mouse", MouseFragment.class));
+        ab.addTab(tab);
+
+        tab = ab.newTab();
+        tab.setText(R.string.tab_keyboard_qwerty);
+        tab.setTabListener(new TabListener<KeyboardFragment>(this, "kbd-qwerty",
+                KeyboardFragment.class, KeyboardFragment.KEYBOARD_TYPE_QWERTY));
+        ab.addTab(tab);
+
+        tab = ab.newTab();
+        tab.setText(R.string.tab_keyboard_numeric);
+        tab.setTabListener(new TabListener<KeyboardFragment>(this, "kbd-num",
+                KeyboardFragment.class, KeyboardFragment.KEYBOARD_TYPE_NAVIGATION_AND_NUMERIC));
+        ab.addTab(tab);
+
+        if (savedInstanceState != null) {
+            ab.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
+        }
+
+        Intent intent = new Intent(this, HidWrapperService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "onDestroy");
+
+        if (mHidWrapper != null) {
+            unbindService(mConnection);
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.actions, menu);
+
+        BluetoothDevice plugdev = (mHidWrapper != null ? mHidWrapper.getPluggedDevice() : null);
+
+        MenuItem piph = menu.findItem(R.id.action_pluginfo_placeholder);
+        if (plugdev != null) {
+            String name = plugdev.getName();
+
+            if (name == null) {
+                name = plugdev.getAddress();
+            }
+
+            piph.setTitle(getResources().getString(R.string.plugged, name));
+        } else {
+            piph.setTitle(R.string.unplugged);
+        }
+
+        menu.findItem(R.id.action_proto_boot).setVisible(mBootMode);
+        menu.findItem(R.id.action_proto_report).setVisible(!mBootMode);
+
+        menu.findItem(R.id.action_connect).setVisible(!mConnected);
+        menu.findItem(R.id.action_disconnect).setVisible(mConnected);
+
+        menu.findItem(R.id.action_connect).setEnabled(mAppReady && plugdev != null);
+        menu.findItem(R.id.action_disconnect).setEnabled(mAppReady && plugdev != null);
+
+        menu.findItem(R.id.action_unplug).setEnabled(mConnected && mAppReady);
+
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.action_connect:
+                mHidWrapper.connect();
+                return true;
+
+            case R.id.action_disconnect:
+                mHidWrapper.disconnect();
+                return true;
+
+            case R.id.action_unplug:
+                AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                builder.setTitle(R.string.unplug_title).setMessage(R.string.unplug_message);
+                builder.setPositiveButton(android.R.string.yes,
+                        new DialogInterface.OnClickListener() {
+
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                mHidWrapper.unplug();
+                            }
+                        });
+                builder.setNegativeButton(android.R.string.no, null);
+                builder.create().show();
+                return true;
+        }
+
+        return super.onContextItemSelected(item);
+    }
+
+    HidWrapperService getHidWrapper() {
+        return mHidWrapper;
+    }
+
+    @Override
+    public void onKeyboardLedState(boolean numLock, boolean capsLock, boolean scrollLock) {
+        Log.v(TAG, "onKeyboardLedState(): numLock=" + numLock + " capsLock=" + capsLock
+                + " scrollLock=" + scrollLock);
+
+        mNumLockLed = numLock;
+        mCapsLockLed = capsLock;
+        mScrollLockLed = scrollLock;
+
+        // TODO: make tags constants or sth
+        for (String tag : new String[] {
+                "kbd-qwerty", "kbd-num"
+        }) {
+            KeyboardFragment fr = (KeyboardFragment) getFragmentManager().findFragmentByTag(tag);
+
+            if (fr != null) {
+                fr.updateLeds(numLock, capsLock, scrollLock);
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
+    }
+
+    @Override
+    public void onApplicationState(boolean registered) {
+        mAppReady = registered;
+        invalidateOptionsMenu();
+    }
+
+    @Override
+    public void onConnectionState(BluetoothDevice device, boolean connected) {
+        mConnected = connected;
+        invalidateOptionsMenu();
+    }
+
+    @Override
+    public void onProtocolModeState(boolean bootMode) {
+        mBootMode = bootMode;
+        invalidateOptionsMenu();
+    }
+
+    @Override
+    public void onPluggedDeviceChanged(BluetoothDevice device) {
+        invalidateOptionsMenu();
+    }
+
+    void queryLeds(KeyboardFragment fr) {
+        fr.updateLeds(mNumLockLed, mCapsLockLed, mScrollLockLed);
+    }
+}
diff --git a/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/MouseFragment.java b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/MouseFragment.java
new file mode 100644
index 0000000..3d8e52d
--- /dev/null
+++ b/hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/MouseFragment.java
@@ -0,0 +1,282 @@
+/*
+ * 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.hiddtestapp;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ToggleButton;
+
+public class MouseFragment extends Fragment {
+
+    private MainActivity mActivity;
+
+    private View mTouchpad;
+
+    private View mScrollZone;
+
+    private int mSpeed = 3;
+
+    private int mScrollSpeed = 3;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                                            Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.mouse_fragment, container, false);
+
+        // setup touchpad
+        mTouchpad = view.findViewById(R.id.touchpad);
+        mTouchpad.setOnTouchListener(new OnTouchListener() {
+
+            private int mPrevX;
+
+            private int mPrevY;
+
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        mPrevX = (int) (event.getX() * mSpeed);
+                        mPrevY = (int) (event.getY() * mSpeed);
+                        break;
+
+                    case MotionEvent.ACTION_MOVE:
+                        int x = (int) (event.getX() * mSpeed);
+                        int y = (int) (event.getY() * mSpeed);
+
+                        mActivity.getHidWrapper().mouseMove(x - mPrevX, y - mPrevY);
+
+                        mPrevX = x;
+                        mPrevY = y;
+                        break;
+                }
+
+                return true;
+            }
+        });
+
+        // setup scroll
+        mScrollZone = view.findViewById(R.id.scrollzone);
+        mScrollZone.setOnTouchListener(new OnTouchListener() {
+
+            private int mPrevY;
+
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        mPrevY = (int) (event.getY() * mScrollSpeed);
+                        break;
+
+                    case MotionEvent.ACTION_MOVE:
+                        int y = (int) (event.getY() * mScrollSpeed);
+
+                        mActivity.getHidWrapper().mouseScroll((byte) (mPrevY - y));
+
+                        mPrevY = y;
+                        break;
+                }
+
+                return true;
+            }
+        });
+
+        // setup buttons
+        ViewGroup bar = (ViewGroup) view.findViewById(R.id.buttons);
+        int count = bar.getChildCount();
+
+        for (int i = 0; i < count; i++) {
+            View child = bar.getChildAt(i);
+
+            try {
+                Button button = (Button) child;
+                button.setOnTouchListener(new OnTouchListener() {
+
+                    @Override
+                    public boolean onTouch(View v, MotionEvent event) {
+                        int which = Integer.valueOf((String) v.getTag());
+
+                        switch (event.getAction()) {
+                            case MotionEvent.ACTION_DOWN:
+                                mActivity.getHidWrapper().mouseButtonDown(which);
+                                break;
+
+                            case MotionEvent.ACTION_UP:
+                                mActivity.getHidWrapper().mouseButtonUp(which);
+                                break;
+                        }
+
+                        return false;
+                    }
+
+                });
+            } catch (ClassCastException e) {
+                // not a button :)
+            }
+        }
+
+        // setup speed controls
+        bar = (ViewGroup) view.findViewById(R.id.speed_control);
+        count = bar.getChildCount();
+
+        for (int i = 0; i < count; i++) {
+            View child = bar.getChildAt(i);
+
+            try {
+                ToggleButton button = (ToggleButton) child;
+                button.setOnClickListener(new OnClickListener() {
+
+                    @Override
+                    public void onClick(View v) {
+                        ToggleButton button = (ToggleButton) v;
+
+                        // do not allow to uncheck button
+                        if (!button.isChecked()) {
+                            button.setChecked(true);
+                            return;
+                        }
+
+                        updateSpeed(Integer.parseInt((String) button.getTag()));
+                    }
+
+                });
+            } catch (ClassCastException e) {
+                // not a button :)
+            }
+        }
+
+        // setup scroll speed controls
+        bar = (ViewGroup) view.findViewById(R.id.scroll_speed_control);
+        count = bar.getChildCount();
+
+        for (int i = 0; i < count; i++) {
+            View child = bar.getChildAt(i);
+
+            try {
+                ToggleButton button = (ToggleButton) child;
+                button.setOnClickListener(new OnClickListener() {
+
+                    @Override
+                    public void onClick(View v) {
+                        ToggleButton button = (ToggleButton) v;
+
+                        // do not allow to uncheck button
+                        if (!button.isChecked()) {
+                            button.setChecked(true);
+                            return;
+                        }
+
+                        updateScrollSpeed(Integer.parseInt((String) button.getTag()));
+                    }
+
+                });
+            } catch (ClassCastException e) {
+                // not a button :)
+            }
+        }
+
+        return view;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mActivity = (MainActivity) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(this.getClass().getSimpleName()
+                    + " can be only attached to " + MainActivity.class.getSimpleName());
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        // intial value
+        updateSpeed(3);
+        updateScrollSpeed(3);
+    }
+
+    private void updateSpeed(int newSpeed) {
+        // note: we assume at least button have proper speed-tag so this will
+        // check what it should
+
+        mSpeed = newSpeed;
+
+        ViewGroup bar = (ViewGroup) getView().findViewById(R.id.speed_control);
+        int count = bar.getChildCount();
+
+        for (int i = 0; i < count; i++) {
+            View child = bar.getChildAt(i);
+
+            try {
+                ToggleButton button = (ToggleButton) child;
+
+                int speed = Integer.parseInt((String) button.getTag());
+
+                button.setChecked(speed == newSpeed);
+            } catch (ClassCastException e) {
+                // not a button :)
+            }
+        }
+    }
+
+    private void updateScrollSpeed(int newSpeed) {
+        // note: we assume at least button have proper speed-tag so this will
+        // check what it should
+
+        mScrollSpeed = newSpeed;
+
+        ViewGroup bar = (ViewGroup) getView().findViewById(R.id.scroll_speed_control);
+        int count = bar.getChildCount();
+
+        for (int i = 0; i < count; i++) {
+            View child = bar.getChildAt(i);
+
+            try {
+                ToggleButton button = (ToggleButton) child;
+
+                int speed = Integer.parseInt((String) button.getTag());
+
+                button.setChecked(speed == newSpeed);
+            } catch (ClassCastException e) {
+                // not a button :)
+            }
+        }
+    }
+}