summaryrefslogtreecommitdiffstats
path: root/hiddtestapp
diff options
context:
space:
mode:
authorHemant Gupta <hemantg@codeaurora.org>2013-11-11 13:04:24 +0530
committerGerrit - the friendly Code Review server <code-review@localhost>2013-11-20 00:42:33 -0800
commitfb0fb83fa3c14c05dbc62ed10541147da9ede51c (patch)
tree5383a35b3602ccc1f3ec8beb71337947f3ee36f6 /hiddtestapp
parent2b929cdac2e37867038172b5f9d002f13420dada (diff)
downloadandroid_packages_apps_BluetoothExt-fb0fb83fa3c14c05dbc62ed10541147da9ede51c.tar.gz
android_packages_apps_BluetoothExt-fb0fb83fa3c14c05dbc62ed10541147da9ede51c.tar.bz2
android_packages_apps_BluetoothExt-fb0fb83fa3c14c05dbc62ed10541147da9ede51c.zip
Bluetooth: HID: Add test app for HID Device Role
This patch adds HID Device Test application for testing Keyboard and Mouse Role supported by Phone. Change-Id: Iad428d761a4235c9a8a216a3202af9b456d652e9 CRs-Fixed: 573086
Diffstat (limited to 'hiddtestapp')
-rw-r--r--hiddtestapp/Android.mk16
-rw-r--r--hiddtestapp/AndroidManifest.xml60
-rw-r--r--hiddtestapp/res/drawable-hdpi/ic_launcher.pngbin0 -> 4586 bytes
-rw-r--r--hiddtestapp/res/drawable-mdpi/ic_launcher.pngbin0 -> 2499 bytes
-rw-r--r--hiddtestapp/res/drawable-xhdpi/ic_launcher.pngbin0 -> 6079 bytes
-rw-r--r--hiddtestapp/res/layout/activity_main.xml35
-rw-r--r--hiddtestapp/res/layout/keyboard_fragment.xml66
-rw-r--r--hiddtestapp/res/layout/mouse_fragment.xml144
-rw-r--r--hiddtestapp/res/layout/numeric_keyboard.xml52
-rw-r--r--hiddtestapp/res/menu/actions.xml57
-rw-r--r--hiddtestapp/res/values-v11/styles.xml38
-rw-r--r--hiddtestapp/res/values-v14/styles.xml39
-rw-r--r--hiddtestapp/res/values/colors.xml33
-rw-r--r--hiddtestapp/res/values/strings.xml55
-rw-r--r--hiddtestapp/res/values/styles.xml42
-rw-r--r--hiddtestapp/res/xml/navigation_keyboard.xml60
-rw-r--r--hiddtestapp/res/xml/numeric_keyboard.xml66
-rw-r--r--hiddtestapp/res/xml/qwerty_keyboard.xml117
-rw-r--r--hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/HidConsts.java159
-rw-r--r--hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/HidWrapperService.java562
-rw-r--r--hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/Key.java116
-rw-r--r--hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/Keyboard.java200
-rw-r--r--hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/KeyboardFragment.java146
-rw-r--r--hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/MainActivity.java300
-rw-r--r--hiddtestapp/src/org/codeaurora/bluetooth/hiddtestapp/MouseFragment.java282
25 files changed, 2645 insertions, 0 deletions
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 :)
+ }
+ }
+ }
+}