Android基于wifi的无线HID设备实现

Posted createchance

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android基于wifi的无线HID设备实现相关的知识,希望对你有一定的参考价值。

2016.06.28更新
现在我将android端作为client,pc端作为server端。目的就是为了让android端能够更加灵活地链接pc端。主要修改如下:
1. 修改整体架构,将pc端作为server端,android端作为client端。 pc端一直在监听来自android端的消息,如果android端有链接请求则建立请求; 如果android端断开链接则pc端继续监听,直到有链接消息。
2. 增加服务自动发现机制,pc端只需要运行程序即可,android端也只需要 打开app,然后app会通过UDP的224.0.0.1地址进行组播查找服务,如果找到 服务,则向服务器发起链接。
3. 修改音量键对应的键值,音量下键对应方向键下键,音量上键对应方向键上键。
详细的代码可以查看我的开源项目主页:https://github.com/CreateChance/WirelessHid
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
偶然间突发奇想,想到能不能让我们的在我们的手机设备上滑动触摸屏进而控制pc上的鼠标移动,也就说把我们的android设备当成是pc设备的触摸板呢?要想实现这个目标,首先要想一想android设备和pc设备之间的通讯基础是什么?这个通讯技术必须是android和pc同时支持的,目前看来也就是wifi,蓝牙。首先说一下蓝牙,蓝牙是一个提供个人局域网的安全无线电通讯技术,相对于wifi而言,蓝牙的功耗相对较低,尤其是BLE技术使得蓝牙的功耗可以和zigbee媲美了,并且android也支持了基于蓝牙的socket操作。但是pc上的java部分对于蓝牙的socket支持就不是很好了,实现起来比较麻烦。但是wifi虽然功耗相对蓝牙而言比较高了点,但是实现起来非常容易,就是socket就好了!所以在第一版本中,可以先使用wifi作为传输技术。
解决了传输技术之后,还需要解决的是都有哪些数据类型,怎么传递数据,使用什么样的协议的问题。这些问题很关键,这涉及到以后的程序可扩展性问题,如果这部分欠缺考虑的话,那么后期的修改和扩展将是一个灾难。进过仔细考量之后,决定采用google的protobuf来封装所有的数据,因为protobuf灵活,小巧,高效,正好就是我要的。
进过了几天的业余时间开发,终于出来了一个可以运行展示的初级版本,这个版本可以满足基本的需求。目前我已经将这个代码开源出来了,项目地址是github:https://github.com/CreateChance/WirelessHid

UI的设计

我要做的就是使用手机实现一个touchpad和keyboard,这就决定了UI的设计必须符合我们日常见到的实体touchpad和keyboard的样式。进过设计之后,touchpad部分设计为一个fragment,它的布局如下:

<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="65dip"
            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="65dip"
            android:layout_height="match_parent"
            android:background="#2b2b2b" />

        <LinearLayout
            android:id="@+id/scroll_speed_control"
            android:layout_width="65dip"
            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="65dip"
        android:orientation="horizontal" >

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:tag="0"
            android:text="left"
            android:id="@+id/left_button" />

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:tag="1" />

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:tag="2"
            android:id="@+id/right_button"
            android:text="right" />

    </LinearLayout>

</LinearLayout>

运行时的效果如下:

键盘的布局就比较复杂了,这部分也是一个fragment,整体布局如下:

<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="@color/led_off"
            style="@style/keyboard_led" />

        <TextView
            android:id="@+id/led_capslock"
            android:text="@string/led_capslock"
            android:background="@color/led_off"
            style="@style/keyboard_led" />

        <TextView
            android:id="@+id/led_scrolllock"
            android:text="@string/led_scrolllock"
            android:background="@color/led_off"
            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>

这个布局中首先放上3个textview在一个LinearLayout这三个textview充当真实键盘上的3个led灯:num lock, caps lock, scroll lock。然后就是一个存放实际keyboard布局的LinearLayout容器,这么做的目的是这样的:因为手机的屏幕很小,想要放下一个标准键盘上的所有的按键肯定是不行的,因此需要将键盘分区,然后分别展示,这里的这个容器就是用来存放不同分区,不同布局的键盘部分。目前我把键盘分成了2个部分:主键盘部分,导航部分加上数字部分。其中主键盘部分是我们最常使用的部分,这部分包含了26个字母,0~9数字(字母上排),12个功能键等,导航部分就是上下左右键盘,上页下页部分等,数字部分就是数字小键盘和一些控制按键,我把导航键和数字键合并在一起了,这两部分的布局如下:
主键盘部分:

<Keyboard>
    <Layout>
        <Key keyLabel="Esc" keyCode="27"/>
        <Key keyLabel="F1" keyCode="112"/>
        <Key keyLabel="F2" keyCode="113"/>
        <Key keyLabel="F3" keyCode="114"/>
        <Key keyLabel="F4" keyCode="115"/>
        <Key keyLabel="F5" keyCode="116"/>
        <Key keyLabel="F6" keyCode="117"/>
        <Key keyLabel="F7" keyCode="118"/>
        <Key keyLabel="F8" keyCode="119"/>
        <Key keyLabel="F9" keyCode="120"/>
        <Key keyLabel="F10" keyCode="121"/>
        <Key keyLabel="F11" keyCode="122"/>
        <Key keyLabel="F12" keyCode="123"/>
        <Key keyLabel="Del" keyCode="127"/>
    </Layout>
    <Layout>
        <Key keyLabel="'" shiftLabel="~" keyCode="192"/>
        <Key keyLabel="1" shiftLabel="!" keyCode="49"/>
        <Key keyLabel="2" shiftLabel="\\@" keyCode="50"/>
        <Key keyLabel="3" shiftLabel="\\#" keyCode="51"/>
        <Key keyLabel="4" shiftLabel="$" keyCode="52"/>
        <Key keyLabel="5" shiftLabel="%" keyCode="53"/>
        <Key keyLabel="6" shiftLabel="^" keyCode="54"/>
        <Key keyLabel="7" shiftLabel="&amp;" keyCode="55"/>
        <Key keyLabel="8" shiftLabel="*" keyCode="56"/>
        <Key keyLabel="9" shiftLabel="(" keyCode="57"/>
        <Key keyLabel="0" shiftLabel=")" keyCode="48"/>
        <Key keyLabel="-" shiftLabel="_" keyCode="45"/>
        <Key keyLabel="=" shiftLabel="+" keyCode="61"/>
        <Key keyLabel="Backspace &#x2190;" keyCode="8" weight="1.5"/>
    </Layout>
    <Layout>
        <Key keyLabel="Tab &#x21B9;" keyCode="9" weight="1.5"/>
        <Key keyLabel="Q" keyCode="81"/>
        <Key keyLabel="W" keyCode="87"/>
        <Key keyLabel="E" keyCode="69"/>
        <Key keyLabel="R" keyCode="82"/>
        <Key keyLabel="T" keyCode="84"/>
        <Key keyLabel="Y" keyCode="89"/>
        <Key keyLabel="U" keyCode="85"/>
        <Key keyLabel="I" keyCode="73"/>
        <Key keyLabel="O" keyCode="79"/>
        <Key keyLabel="P" keyCode="80"/>
        <Key keyLabel="[" keyCode="91" shiftLabel=""/>
        <Key keyLabel="]" keyCode="93" shiftLabel=""/>
        <Key keyLabel="\\\\" keyCode="92" shiftLabel="|"/>
    </Layout>
    <Layout>
        <Key keyLabel="Caps Lock" keyCode="20" weight="1.5"/>
        <Key keyLabel="A" keyCode="65"/>
        <Key keyLabel="S" keyCode="83"/>
        <Key keyLabel="D" keyCode="68"/>
        <Key keyLabel="F" keyCode="70"/>
        <Key keyLabel="G" keyCode="71"/>
        <Key keyLabel="H" keyCode="72"/>
        <Key keyLabel="J" keyCode="74"/>
        <Key keyLabel="K" keyCode="75"/>
        <Key keyLabel="L" keyCode="76"/>
        <Key keyLabel=";" keyCode="59" shiftLabel=":"/>
        <Key keyLabel="'" keyCode="44" shiftLabel="&quot;"/>
        <Key keyLabel="Enter &#x21B5;" keyCode="10" weight="3.0"/>
    </Layout>
    <Layout>
        <Key keyLabel="Shift &#x21E7;" keyCode="16" keyFunc="Shift" weight="1.5"/>
        <Key keyLabel="Z" keyCode="90"/>
        <Key keyLabel="X" keyCode="88"/>
        <Key keyLabel="C" keyCode="67"/>
        <Key keyLabel="V" keyCode="86"/>
        <Key keyLabel="B" keyCode="66"/>
        <Key keyLabel="N" keyCode="78"/>
        <Key keyLabel="M" keyCode="77"/>
        <Key keyLabel="," keyCode="44" shiftLabel="&lt;"/>
        <Key keyLabel="." keyCode="46" shiftLabel="&gt;"/>
        <Key keyLabel="/" keyCode="47" shiftLabel="\\?"/>
        <Key keyLabel="Shift &#x21E7;" keyCode="16" keyFunc="Shift" weight="1.5"/>
    </Layout>
    <Layout>
        <Key keyLabel="Ctrl" keyCode="17" weight="1.5"/>
        <Key keyLabel="Win" keyCode="524"/>
        <Key keyLabel="Alt" keyCode="18"/>
        <Key keyLabel=" " keyCode="32" weight="10.0"/>
        <Key keyLabel="Alt" keyCode="18"/>
        <Key keyLabel="Win" keyCode="524"/>
        <Key keyLabel="Menu" keyCode="93"/>
        <Key keyLabel="Ctrl" keyCode="17" weight="1.5"/>
    </Layout>
</Keyboard>

导航键部分:

<Keyboard>
    <Layout>
        <Key keyLabel="Print\\nScreen" keyCode="154"/>
        <Key keyLabel="Scroll\\nLock" keyCode="145"/>
        <Key keyLabel="Pause\\nBreak" keyCode="19"/>
    </Layout>
    <Layout>
        <Key keyLabel="Insert" keyCode="155"/>
        <Key keyLabel="Home" keyCode="36"/>
        <Key keyLabel="Page Up" keyCode="33"/>
    </Layout>
    <Layout>
        <Key keyLabel="Delete" keyCode="127"/>
        <Key keyLabel="End" keyCode="35"/>
        <Key keyLabel="Page Down" keyCode="34"/>
    </Layout>
    <Layout>
        <Key visible="false"/>
        <Key keyLabel="&#x2191;" keyCode="38"/>
        <Key visible="false"/>
    </Layout>
    <Layout>
        <Key keyLabel="&#x2190;" keyCode="37"/>
        <Key keyLabel="&#x2193;" keyCode="40"/>
        <Key keyLabel="&#x2192;" keyCode="39"/>
    </Layout>
</Keyboard>

数字键部分:

<Keyboard>
    <Layout>
        <Key keyLabel="Num\\nLock" keyCode="144"/>
        <Key keyLabel="/" keyCode="111"/>
        <Key keyLabel="*" keyCode="106"/>
        <Key keyLabel="-" keyCode="45"/>
    </Layout>
    <Layout>
        <Key keyLabel="7\\nHome" keyCode="0103"/>
        <Key keyLabel="8 &#x2191;" keyCode="104"/>
        <Key keyLabel="9\\nPgUp" keyCode="105"/>
        <Key keyLabel="+" keyCode="521"/>
    </Layout>
    <Layout>
        <Key keyLabel="4\\n&#x2190;" keyCode="100"/>
        <Key keyLabel="5" keyCode="101"/>
        <Key keyLabel="6\\n&#x2192;" keyCode="102"/>
        <Key visible="false"/>
    </Layout>
    <Layout>
        <Key keyLabel="1\\nEnd" keyCode="97"/>
        <Key keyLabel="2 &#x2193;" keyCode="98"/>
        <Key keyLabel="3\\nPgDn" keyCode="99"/>
        <Key visible="false"/>
    </Layout>
    <Layout>
        <Key keyLabel="0\\nIns" keyCode="96" weight="2.0"/>
        <Key keyLabel=".\\nDel" keyCode="110"/>
        <Key keyLabel="Enter" keyCode="10"/>
    </Layout>
</Keyboard>

这里的布局需要说明一下,这里我使用了layout标签表明,然后使用XmlResourceParser类来解析这个里面的内容,最后再添加到布局中去。下面贴出两张键盘的运行效果图:
主键盘:

从键盘(导航键和数字键):

代码设计

Server端

Server整体代码就是一个app,内容不是很复杂,这里我只陈述我的代码功能和必要的代码片段,详细代码内容有限于篇幅就不贴出来了,可以查看我的github项目主页(https://github.com/CreateChance/WirelessHid)上的开源代码。
代码的基本分布如下:

各个类的作用如下:

MainActivity

这是主界面类,基本就是MouseFragment的容器,另外就是监听用户点击回退事件,如果用户在1.5s之内连续点击两次回退就退出app,基本逻辑比较简单。

WirelessHidService

这是整个app的服务,这个服务是实际将数据发送出去的地方,主要就是通过looper和handler的方式将消息队列中的数据发送出去。发送部分的逻辑:
一个looper线程

private class DataSendThread extends Thread 

        private OutputStream os = null;

        @Override
        public void run() 
            super.run();

            Looper.prepare();

            try 
                Log.d(TAG, "I'm waiting for connecting.");
                mServerSocket = new ServerSocket(Constant.HID_TCP_PORT);
                mServerSocket.setReuseAddress(true);
                mSocket = mServerSocket.accept();
                os = mSocket.getOutputStream();
                Toast.makeText(getApplicationContext(), "Client connected!",
                        Toast.LENGTH_SHORT).show();
                Log.d(TAG, "client connected!");
             catch (IOException e) 
                e.printStackTrace();
                return;
            

            mDataSendHandler = new Handler() 
                @Override
                public void handleMessage(Message msg) 
                    super.handleMessage(msg);

                    // send data here.
                    try 
                        ((WirelessHidProto.HidData)msg.obj).writeDelimitedTo(os);
                     catch (IOException e) 
                        Log.d(TAG, "IOException, close all resource.");
                        mDataSendHandler = null;
                        if (mListener != null) 
                            mListener.onHandlerChanged(mDataSendHandler);
                        
                        this.getLooper().quit();
                        sendBroadcast(new Intent(ACTION_RESET_CONNECTION));
                     finally 
                        try 
                            mServerSocket.close();
                         catch (IOException e) 
                            e.printStackTrace();
                        
                    
                
            ;

            if (mListener != null) 
                mListener.onHandlerChanged(mDataSendHandler);
            

            Looper.loop();
        
    

界面Fragment使用Handler和server交互,fragment需要实现server的DataHandlerListener接口,当Handler变化的时候通知Fragment,以便Fragment拿到最新的对象引用:
需要实现的接口

public interface DataHandlerListener 
        void onHandlerChanged(Handler handler);

设置listener的接口:

public void setListener(DataHandlerListener listener) 
        this.mListener = listener;

fragment也可以主动获取:

public Handler getDataSendHandler() 
    return this.mDataSendHandler;

这里的发送使用的就是protobuf的序列化接口,关于这个接口的描述这里就不详述,可以参考google protobuf的java部分的编程指导:
https://developers.google.com/protocol-buffers/docs/javatutorial

MouseFragment

这个类是fragment类,主要是嵌套在MainActivity类中,主要逻辑功能如下:
1. 捕获用户触摸屏移动,点击事件

@Override
            public boolean onTouch(View v, MotionEvent event) 
                switch (event.getAction()) 
                    case MotionEvent.ACTION_DOWN:
                        //single and double click handle here.
                        mPrevX = (int) event.getX();
                        mPrevY = (int) event.getY();
                        time = new Date().getTime();
                        break;
                    case MotionEvent.ACTION_UP:
                        if (new Date().getTime() - time < mDoubleClickTimeThreshold) 
                            if ((int) event.getX() - mPrevX < mDoubleClickPosThreshold
                                    && (int) event.getY() - mPrevY < mDoubleClickPosThreshold) 
                                mouseClickPress(Constant.MOUSE_BUTTON_LEFT);
                                mouseClickRelease(Constant.MOUSE_BUTTON_LEFT);
                            
                        

                    case MotionEvent.ACTION_MOVE:
                     

以上是关于Android基于wifi的无线HID设备实现的主要内容,如果未能解决你的问题,请参考以下文章

什么是wifi探针??

基于ZYNQ wifi方案实现与测试

基于lora及wifi无线通信的无线PLC在物联网远程IO控制场合中的应用

确定 Android 设备中的 3G/WIFI 无线电状态机当前状态

8266wifi模块是啥

Android安卓手机怎样连接隐藏的WiFi无线信号