Android基于Socket无线遥控(二)-模拟触摸按键篇

Android基于Socket无线遥控(2)--模拟触摸按键篇
按计划每周更新一篇技术博文,第五篇:《Android基于Socket无线遥控(2)--无线控制篇》

  本篇主要内容涉及模拟系统按键消息,单击事件,触屏事件等,模拟Android系统按键参考了网上资料(见引用1),无线遥控收发信息部分和上节所讲内容一样。

一、技术介绍
1.模拟按键及触屏相关
    模拟按键和触摸屏需要调用SDK内部隐藏方法,在WindowManagerService中实现UI交互的注入方法,但可惜WindowManagerService这个类是被标记了@hide,外部不能使用,不然恶意程序就随意控制Android设备了。
    在WindowManagerService类中,按键和触屏消息的方法如下:
// Injects a keystroke event into the UI.
public boolean injectKeyEvent(KeyEvent ev, boolean sync){
...
}

//  Inject a pointer (touch) event into the UI.
public boolean injectPointerEvent(MotionEvent ev, boolean sync) {
...
}

    更进一步可以搜素相关资料,这里涉及到Binder,还有AIDL(Android接口描述语言),这些内容真要弄明白还需要花些时间,大概作用就是跨进程之间的数据通讯,Binder更是在Java层与C++层之间的数据传输起到很大作用。在Android源码中随处可见,因为实际项目中很少使用,之前看过的内容很快就忘记了。
    WindowManagerService类就是IWindowManager.Stub的实现类,在内部程序中使用如下:
// 模拟触摸消息

private static void sendPointerSync(MotionEvent event) {
try {
(IWindowManager.Stub.asInterface(ServiceManager.getService("window"))).injectPointerEvent(event, true);
} catch (RemoteException e) {}
}

// 模拟按键消息

private static void sendKeySync(KeyEvent event) {
try {
(IWindowManager.Stub.asInterface(ServiceManager.getService("window"))).injectKeyEvent(event, true);
} catch (RemoteException e) {}
}


2.常见按键消息
Key  | Constant Value
Back 4
Power 26
Menu 82
Home 3
Search 84

方向键:
上 19
下 20
左 21
右 22
确认(类似单击):23

音量键:
音量减 25
音量加 24

更详细的KeyEvent请点击:http://developer.android.com/reference/android/view/KeyEvent.html

二、为Android系统添加模拟按键对外接口
步骤一:在Android源码中传建新扩展模块目录
    在源码的frameworks/base/cmds下传建一个文件夹,本例文件夹命名【analog_control】,在此目录下可存放用java或C编写的可运行程序。

步骤二:编写模拟按键相关代码
    在步骤一所创建的目录下新建一个AnalogControl.java文件,编写程序代码,在此你可以使用IWindowManager类,本例需要模拟按键key,长按键keypress,点触笔touch,点触笔长按touchpress,以及移动move等。(本例代码使用引用1的代码)
import android.view.MotionEvent;
import android.view.KeyEvent;
import android.view.IWindowManager;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.RemoteException;
import android.util.Log;
public class AnalogControl {

    public static void main(String args[])throws Exception{
        String[] mArgs = args;
        try
        {
            String opt = mArgs[0];
            if(opt.equals("touch")){
                float x = Float.valueOf(mArgs[1]);
                float y = Float.valueOf(mArgs[2]);
                MotionEvent e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0);
                sendPointerSync(e);
                e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);
                sendPointerSync(e);
            }

            else if(opt.equals("move")){
                float x = Float.valueOf(mArgs[1]);
                float y = Float.valueOf(mArgs[2]);
                float x2 = Float.valueOf(mArgs[3]);
                float y2 = Float.valueOf(mArgs[4]);
                MotionEvent e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0);
                sendPointerSync(e);
                e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE, x, y, 0);
                sendPointerSync(e);
                e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE, x, y, 0);
                sendPointerSync(e);
                e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE, x2, y2, 0);
                sendPointerSync(e);
                e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE, x2, y2, 0);
                sendPointerSync(e);
                e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x2, y2, 0);
                sendPointerSync(e);
            }

            else if(opt.equals("key")){
                int keycode = Integer.valueOf(mArgs[1]);
                KeyEvent k = new KeyEvent(KeyEvent.ACTION_DOWN,keycode);
                sendKeySync(k);
                k = new KeyEvent(KeyEvent.ACTION_UP,keycode);
                sendKeySync(k);
            }
            else if(opt.equals("wait")){
                int millsecond = Integer.valueOf(mArgs[1]);
                Thread.sleep(millsecond);
            }
            else if(opt.equals("keypress")){
                int keycode = Integer.valueOf(mArgs[1]);
                int millsecond = Integer.valueOf(mArgs[2]);
                KeyEvent k = new KeyEvent(KeyEvent.ACTION_DOWN,keycode);
                sendKeySync(k);
                Thread.sleep(millsecond);
                k = new KeyEvent(KeyEvent.ACTION_UP,keycode);
                sendKeySync(k); 
            }
            else if(opt.equals("touchpress")){
                float x = Float.valueOf(mArgs[1]);
                float y = Float.valueOf(mArgs[2]);
                int millsecond = Integer.valueOf(mArgs[3]);
                MotionEvent e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0);
                sendPointerSync(e);
                Thread.sleep(millsecond);
                e = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);
                sendPointerSync(e);
            }
            else System.err.println("** Error: Unknown option: " + opt);
        }
        catch (RuntimeException ex){}
        Thread.sleep(2000);
    }

    private static void sendPointerSync(MotionEvent event) {
        try {
            (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))).injectPointerEvent(event, true);
        } catch (RemoteException e) {}
    }

    private static void sendKeySync(KeyEvent event) {
        try {
            (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))).injectKeyEvent(event, true);
        } catch (RemoteException e) {}
    }
}

步骤三:编写Android.mk文件

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE := analog_control
LOCAL_MODULE_TAGS := optional
include $(BUILD_JAVA_LIBRARY)

步骤四:编译模块并将生成jar报拷贝到设备的/system/framework/下

    启动终端,在Android源码根目录下运行:【. build/envsetup.sh】运行后可使用mm/mmm等命令进行模块编译,再进入步骤一创建的目录analog_control中,使用【mm】进行模块编译,编译后在/out/target/product/generic/system/framework下生成对应的analog_control.jar包。

    将生成的jar包拷贝到设备的/system/framework/下


步骤五:编写可执行Shell文件

传建analog_control文本文件,里边编写如下可执行Shell语句,使得上层应用可以通过执行Shell命令来实现模拟按键。

base=/system
export CLASSPATH=$base/framework/analog_control.jar
exec app_process $base/bin AnalogControl $*

将analog_control文件拷贝到设备的/system/bin/目录下,并修改为可执行权限【chmod 777 analog_control】


完成以上步骤便可在终端中通过命令来模拟消息,Android程序中通过在代码中运行命令行可以执行相应操作,前提需要su权限。
String cmd = "执行命令";

try {

Process proc = Runtime.getRuntime().exec("su");  // 设备需要拥有su权限
proc.getOutputStream().write(cmd.getBytes());
proc.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}

PS:各种触发时间命令如下:
1.touch事件
analog_control touch x y
2.move事件
analog_control move x1 y1 x2 y2
3.key事件
analog_control key 【按键消息代号】 (见上文所提到的常见按键消息
4.keypress事件
analog_control keypress 【按键消息代号】 【长按时间】
5.touchpress事件
analog_control touchpress x y 【长按时间】


三、编码实现
本节添加的源码很少,主要是实现模拟触摸按键,其余代码见上一讲,本节代码添加如下:
1.ServerThread.java
/**
 * 此线程不断接收客户端发过来的请求信息
 */
public void run()
{
    try
    {
        String content = null;
        //采用循环不断从Socket中读取客户端发送过来的数据
        while ((content = readFromClient()) != null)
        {

            if(content.length() < 2) continue; // 避免发送空指令 或错误指令导致异常

            String requestStr = content.substring(0, 2);
            Log.v("", "SocketServer ---> content : " + content + " , requestStr : " + requestStr );

            if(requestStr.equals(OPERATION_VOLUME)){
                MainActivity.systemManager.CtrlVolume(content);
            }else if(requestStr.equals(OPERATION_BRIGHTNESS)){
                // 需要在UI线程中实现 handler
                SystemManager.CtrlBrightness(MainActivity.mActivity,content);

            }
            // 下边所有操作都是通过模拟按键实现
            else if(requestStr.equals(OPERATION_CLICK)){
                String cmd = "analog_control key 23\n";
                SystemManager.execute(cmd);
            }else if(requestStr.equals(OPERATION_MENU)){
                String cmd = "analog_control key 82\n";
                SystemManager.execute(cmd);
            }else if(requestStr.equals(OPERATION_BACK)){
                String cmd = "analog_control key 4\n";
                SystemManager.execute(cmd);
            }else if(requestStr.equals(OPERATION_POWER)){
                String cmd = "analog_control key 26\n";
                SystemManager.execute(cmd);
            }else if(requestStr.equals(OPERATION_HOME)){
                String cmd = "analog_control key 3\n";
                SystemManager.execute(cmd);
            }else if(requestStr.equals(OPERATION_DIRECTION_TOP)){
                String cmd = "analog_control key 19\n";
                SystemManager.execute(cmd);
            }else if(requestStr.equals(OPERATION_DIRECTION_BOTTOM)){
                String cmd = "analog_control key 20\n";
                SystemManager.execute(cmd);
            }else if(requestStr.equals(OPERATION_DIRECTION_LEFT)){
                String cmd = "analog_control key 21\n";
                SystemManager.execute(cmd);
            }else if(requestStr.equals(OPERATION_DIRECTION_RIGHT)){
                String cmd = "analog_control key 22\n";
                SystemManager.execute(cmd);
            }
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}

2.SystemManager.java
    /**
     * 执行命令
     * @param cmd
     * @return
     */
    public static boolean execute(String cmd){
        boolean isSuccess = false;
        try {
            Process proc = Runtime.getRuntime().exec("su");  // 设备需要拥有su权限
            proc.getOutputStream().write(cmd.getBytes());
            proc.getOutputStream().flush();
            isSuccess = true;
        } catch (IOException e) {
            e.printStackTrace();
            isSuccess = false;
        }
        Log.v("", "---> cmd : " + cmd + "  , isSuccess : " + isSuccess);
        return isSuccess;
    }

四、后记&下篇内容
    后记:第二小节通过命令行的方式模拟按键相关操作有个不足之处是每次命令都要延时1秒左右,如果要模拟鼠标就不行了,同事在AnalogControl.java中通过创建ServerSocket指定一个固定的端口号,然后客户端通过IP地址和指定端口号来控制模拟的方式不会延迟,而且不需要执行命令。由于时间关系本例不再涉及。
    下篇主要内容是介绍基于Socket发送文件数据。




参考引用:
1.Android模拟按键——源码环境下开发应用程序 http://hi.baidu.com/zhouhanqing/blog/item/1bfbaec593f4b5a48326acc7.html