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建立虛擬螢幕
我們來看一下裝置有沒有我們建立的觸控式螢幕ShaoTouchScreen//建立觸控式螢幕 通過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); }
到這設別已經建立完成,接下來我們需要發出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座標的轉換