1. 程式人生 > >Android 通過uinput模擬touch事件發出onActionDown onActionUp onActionMove

Android 通過uinput模擬touch事件發出onActionDown onActionUp onActionMove

     手機中的螢幕觸控事件是通過驅動將事件上報到/dev/input裝置上,然後被input模組讀取傳送到APP

     如果我沒有物理的螢幕但我想發出觸控事件怎麼辦?通過Linux的uinput模組就可以不需要寫驅動程式碼就能模擬一塊觸控式螢幕,當然我們也可以模擬出虛擬滑鼠和鍵盤

     本文討論的是模擬觸控式螢幕,滑鼠和鍵盤比較easy

      前提準備:getevent命令使用

      1通過adb shell getevent -p 檢視裝置的輸入裝置

     

       /dev/input/event0...n就是輸入裝置 KEY一般對應的是鍵值Keycode 一般指裝置上的物理按鍵和虛擬按鍵ABS代表絕對值得意思

    2:adb shell getevent -l /dev/input/event1 獲得該裝置底層傳送的事件,我們看一下手機螢幕被觸控後getevent獲得的事件

       

  我們可以看到 有一系列的按鍵碼被髮出組合成onActionDown-->onActionMove-->onActionUp事件流

 到此我們要做的就是建立一個/dev/input/eventX代表虛擬螢幕, 然後發出按鍵碼形成觸控事件送到input模組發給APP

 一:通過uinput建立虛擬螢幕

//建立觸控式螢幕 通過systemproperty儲存fd
static void createTouchScreen()
{
        static int uinp_fd;
		struct uinput_user_dev uinp;
		struct input_event event;
		uinp_fd = open("/dev/uinput", O_WRONLY|O_NONBLOCK);
		if(uinp_fd == 0) {
			ALOGD("Unable to open /dev/uinput\n");
			return;
		}

		// configure touch device event properties
		memset(&uinp, 0, sizeof(uinp));
                //裝置的別名
		strncpy(uinp.name, "ShaoTouchScreen", UINPUT_MAX_NAME_SIZE);
		uinp.id.version = 4;
		uinp.id.bustype = BUS_USB;
		uinp.absmin[ABS_MT_SLOT] = 0;
		uinp.absmax[ABS_MT_SLOT] = 9; // MT代表multi touch 多指觸控 最大手指的數量我們設定9
		uinp.absmin[ABS_MT_TOUCH_MAJOR] = 0;
		uinp.absmax[ABS_MT_TOUCH_MAJOR] = 15;
		uinp.absmin[ABS_MT_POSITION_X] = 0; // 螢幕最小的X尺寸
		uinp.absmax[ABS_MT_POSITION_X] = 1020; // 螢幕最大的X尺寸
		uinp.absmin[ABS_MT_POSITION_Y] = 0; // 螢幕最小的Y尺寸
		uinp.absmax[ABS_MT_POSITION_Y] = 1020; //螢幕最大的Y尺寸
		uinp.absmin[ABS_MT_TRACKING_ID] = 0;
		uinp.absmax[ABS_MT_TRACKING_ID] = 65535;//按鍵碼ID累計疊加最大值
		uinp.absmin[ABS_MT_PRESSURE] = 0;   
		uinp.absmax[ABS_MT_PRESSURE] = 255;     //螢幕按下的壓力值

		// Setup the uinput device
		ioctl(uinp_fd, UI_SET_EVBIT, EV_KEY);   //該裝置支援按鍵
		ioctl(uinp_fd, UI_SET_EVBIT, EV_REL);   //支援滑鼠
    
		// Touch
		ioctl (uinp_fd, UI_SET_EVBIT,  EV_ABS); //支援觸控
		ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_SLOT);
		ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
		ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
		ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
		ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
		ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);    
		ioctl (uinp_fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);

		char str[20];
		memset(str,0,sizeof(str));
		sprintf(str,"%d",uinp_fd);

		property_set("nvr_touch_screen_device",str);
		ALOGD("nvr touch screen device strfd = %s , id = %d\n",str ,uinp_fd);
    
		/* Create input device into input sub-system */
		write(uinp_fd, &uinp, sizeof(uinp));
		ioctl(uinp_fd, UI_DEV_CREATE);

}
 我們來看一下裝置有沒有我們建立的觸控式螢幕ShaoTouchScreen
 

到這設別已經建立完成,接下來我們需要發出touch事件,MULTI_TOUCH 支援多點觸控,我們這邊模擬的是MultiTouch下單點觸控

#include <jni.h>
#include <stdint.h>
#include <cutils/log.h>
#include <linux/uinput.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h> 
#include <cutils/properties.h>


#define ACTION_DOWN 0
#define ACTION_UP 1
#define ACTION_MOVE 2

#define API_EXPORT __attribute__((visibility("default")))

static int global_tracking_id = 1;

namespace shao{

extern long jptr(void *native_dtr) {
    return reinterpret_cast<intptr_t>(native_dtr);
};


extern void *native(long ptr) {
    return reinterpret_cast<void *>(ptr);
};

}

using namespace nvr;

extern "C"{

	static bool device_writeEvent(int fd, uint16_t type, uint16_t keycode, int32_t value) {
    struct input_event ev;  
  
    memset(&ev, 0, sizeof(struct input_event));  
  
    ev.type = type;  
    ev.code = keycode;  
    ev.value = value;
    if (write(fd, &ev, sizeof(struct input_event)) < 0) {
		char * mesg = strerror(errno);
        ALOGD("nibiru uinput errormag info :%s\n",mesg); 
        return false;
    }  

    return true;
   }

	static void execute_sleep(int duration_msec)
	{
		usleep(duration_msec*1000); 
	}

        
        //startX startY 代表觸控down的座標 endX 和 endY代表Up的座標
        //如果startX = startY 同時  endX = endY ,沒有actionMove事件產生只有actionDown和actionUp事件
	API_EXPORT void nvr_execute_touch(int fd,int startX,int startY,int endX,int endY)
	{	
                //actionDown事件 
		device_writeEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, global_tracking_id++);
		device_writeEvent(fd, EV_ABS, ABS_MT_POSITION_X, startX);
		device_writeEvent(fd, EV_ABS, ABS_MT_POSITION_Y, startY);
		device_writeEvent(fd, EV_ABS, ABS_MT_PRESSURE, 60);
		device_writeEvent(fd, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
		device_writeEvent(fd, EV_SYN, SYN_REPORT, 0);
 
                //action_move事件
		device_writeEvent(fd, EV_ABS, ABS_MT_POSITION_X, endX);
		device_writeEvent(fd, EV_ABS, ABS_MT_POSITION_Y, endY);
		device_writeEvent(fd, EV_SYN, SYN_REPORT, 0);
                 
                //action_up事件
		device_writeEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, -1);
                //事件傳送完畢需要sync 
		device_writeEvent(fd, EV_SYN, SYN_REPORT, 0);
		ALOGD(" one touch operation send end");
	}


	API_EXPORT void sendScreenTouch(int startX,int startY,int endX,int endY)
	{
	
		char package_status[PROPERTY_VALUE_MAX]={0};
		property_get("touch_screen_device",package_status,NULL);
		int fd =  atoi(package_status);
        ALOGD("touch screen fd = %d\n",fd);
		nvr_execute_touch(fd,startX,startY,endX,endY);
		execute_sleep(20);
	}

}

到這我們就可以模擬SingleTouch了,如果需要模擬多點觸控的話同樣的思路,按鍵碼可能不一致需要組合

到這一旦傳送成功事件會被input模組識別,會對我們的X和Y座標進行運算,這邊將程式碼路徑貼出來作為提示

 涉及程式碼路徑:\frameworks\native\services\inputflinger\inputreader.cpp

void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
    size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
    size_t outCount = 0;
    BitSet32 newPointerIdBits;

    for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
        const MultiTouchMotionAccumulator::Slot* inSlot =
                mMultiTouchMotionAccumulator.getSlot(inIndex);
        if (!inSlot->isInUse()) {
            continue;
        }

        if (outCount >= MAX_POINTERS) {
#if DEBUG_POINTERS
            ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
                    "ignoring the rest.",
                    getDeviceName().string(), MAX_POINTERS);
#endif
            break; // too many fingers!
        }

        RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount];
        outPointer.x = inSlot->getX();
        outPointer.y = inSlot->getY();
        outPointer.pressure = inSlot->getPressure();
        outPointer.touchMajor = inSlot->getTouchMajor();
        outPointer.touchMinor = inSlot->getTouchMinor();
        outPointer.toolMajor = inSlot->getToolMajor();
        outPointer.toolMinor = inSlot->getToolMinor();
        outPointer.orientation = inSlot->getOrientation();
        outPointer.distance = inSlot->getDistance();
        outPointer.tiltX = 0;
        outPointer.tiltY = 0;

		ALOGD("shao fwk receive multi x = %d , y = %d\n", outPointer.x , outPointer.y);

        outPointer.toolType = inSlot->getToolType();
        if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
            outPointer.toolType = mTouchButtonAccumulator.getToolType();
            if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
                outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
            }
        }

 MultiTouchInputMapper::syncTouch()函式裡會拿到event資料

 在 TouchInputMapper::cookPointerData() 函式裡會根據螢幕方向來計算x,y座標傳給app

        switch (mSurfaceOrientation) {
        case DISPLAY_ORIENTATION_90:
            //x = float(yTransformed - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
            //y = float(mRawPointerAxes.x.maxValue - xTransformed) * mXScale + mXTranslate;
            x = xTransformed;
		    y = yTransformed;
		    ALOGD("shao fwk hadlere 1 x = %f , y = %f \n",x ,y);
            left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
            right = float(rawBottom- mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
            bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
            top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
            orientation -= M_PI_2;
            if (mOrientedRanges.haveOrientation && orientation < mOrientedRanges.orientation.min) {
                orientation += (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
            }
            break;
        case DISPLAY_ORIENTATION_180:
            x = float(mRawPointerAxes.x.maxValue - xTransformed) * mXScale + mXTranslate;
            y = float(mRawPointerAxes.y.maxValue - yTransformed) * mYScale + mYTranslate;
		    ALOGD("shao fwk hadlere 2 x = %f , y = %f\n",x ,y);
            left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
            right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
            bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
            top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
            orientation -= M_PI;
            if (mOrientedRanges.haveOrientation && orientation < mOrientedRanges.orientation.min) {
                orientation += (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
            }
            break;
        case DISPLAY_ORIENTATION_270:
            x = float(mRawPointerAxes.y.maxValue - yTransformed) * mYScale + mYTranslate;
            y = float(xTransformed - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
		    ALOGD("shao fwk hadlere 3 x = %f , y = %f\n",x ,y);
            left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
            right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
            bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
            top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
            orientation += M_PI_2;
            if (mOrientedRanges.haveOrientation && orientation > mOrientedRanges.orientation.max) {
                orientation -= (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
            }
            break;
        default:
            x = float(xTransformed - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
            y = float(yTransformed - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
			ALOGD("shao fwk hadlere 4 x = %f , y = %f\n",x ,y);

所以大家注意一下x y座標的轉換