diff options
author | Hemant Gupta <hemantg@codeaurora.org> | 2013-11-11 13:04:24 +0530 |
---|---|---|
committer | Gerrit - the friendly Code Review server <code-review@localhost> | 2013-11-20 00:42:33 -0800 |
commit | fb0fb83fa3c14c05dbc62ed10541147da9ede51c (patch) | |
tree | 5383a35b3602ccc1f3ec8beb71337947f3ee36f6 /hiddtestapp | |
parent | 2b929cdac2e37867038172b5f9d002f13420dada (diff) | |
download | android_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')
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 Binary files differnew file mode 100644 index 0000000..f83649f --- /dev/null +++ b/hiddtestapp/res/drawable-hdpi/ic_launcher.png diff --git a/hiddtestapp/res/drawable-mdpi/ic_launcher.png b/hiddtestapp/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..8c4edba --- /dev/null +++ b/hiddtestapp/res/drawable-mdpi/ic_launcher.png diff --git a/hiddtestapp/res/drawable-xhdpi/ic_launcher.png b/hiddtestapp/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..b4d5d6c --- /dev/null +++ b/hiddtestapp/res/drawable-xhdpi/ic_launcher.png 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 ⇩" keyCode="0x87"/> + <Key keyLabel="Mute" keyCode="0x85"/> + <Key keyLabel="Vol Up ⇧" 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="↑" keyCode="0x52"/> + <Key visible="false"/> + </Layout> + <Layout> + <Key keyLabel="←" keyCode="0x50"/> + <Key keyLabel="↓" keyCode="0x51"/> + <Key keyLabel="→" 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⇦" keyCode="0x80"/> + <Key keyLabel="Next\n⇨" 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 ↑" keyCode="0x60"/> + <Key keyLabel="9\nPgUp" keyCode="0x61"/> + <Key keyLabel="+" keyCode="0x57"/> + </Layout> + <Layout> + <Key keyLabel="4\n←" keyCode="0x5C"/> + <Key keyLabel="5" keyCode="0x5D"/> + <Key keyLabel="6\n→" keyCode="0x5E"/> + <Key visible="false"/> + </Layout> + <Layout> + <Key keyLabel="1\nEnd" keyCode="0x59"/> + <Key keyLabel="2 ↓" 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="&" 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 ←" keyCode="0x2A" weight="1.5"/> + </Layout> + <Layout> + <Key keyLabel="Tab ↹" 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="""/> + <Key keyLabel="Enter ↵" keyCode="0x28" weight="3.0"/> + </Layout> + <Layout> + <Key keyLabel="Shift ⇧" 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="<"/> + <Key keyLabel="." keyCode="0x37" shiftLabel=">"/> + <Key keyLabel="/" keyCode="0x38" shiftLabel="\?"/> + <Key keyLabel="Shift ⇧" 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 :) + } + } + } +} |