linux 在应用层创建虚拟hid设备

主要功能
  应用层发送鼠标数据给驱动,驱动会解析完,传到input子系统,这样,其他的应用程序就能通用了。
具体步骤
  应用层通过调用uhid驱动,来让uhid驱动创建一个虚拟的hid设备,然后,应用层向uhid发送hid报告描述符,用于生成具体的hid设备。例如,报告描述符包含鼠标描述符的话,就会创建一个input设备,报告描述符包含鼠标和键盘描述符的话,就会创建两个input设备,分别代表鼠标和键盘。接着,应用层发送hid报告数据给uhid驱动,虚拟hid解析完,就会将数据转发到input子系统。

使用步骤

  1. 使能uhid驱动
    在内核配置文件中加入:CONFIG_UHID=y。该驱动源文件的具体路径为drivers/hid/uhid.c。
    成功并烧写到系统的话,在/dev/目录下,会多出一个uhid文件。
  2. 编写应用程序
    下面的代码,会创建一个鼠标,一个键盘设备,并且向hid发送鼠标数据,input子系统会报告事件的产生。

【uhid_control.h】

#pragma once

struct MT_event {
	int state;
	int x; // 0-7808
	int y; // 0-4480
};

struct Keyboard_Ext {
	bool LeftControl;
	bool LeftShift;
	bool LeftAlt;
	bool Left_GUI;
	bool RightControl;
	bool RightShift;
	bool RightAlt;
	bool Right_GUI;
};
void uhid_create(char *descriptor, int size);
void uhid_create_default(bool mouse_enable, bool keyboard_enable);
void uhid_send_report(const char *data, int size);
void uhid_send_default_mouse(bool left_btn, bool right_btn, bool middle_btn, int x, int y, int wheel);
void uhid_send_default_keyboard(struct Keyboard_Ext ext, int key[6]);
void uhid_close(void);
void uhid_test(void);

void multi_touch_create(void);
void multi_touch_set(struct MT_event *event, int count);
void multi_touch_close(void);
void multi_touch_test(void);

【uhid_control.cpp】

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <string>
#include <linux/uhid.h>
#include "uhid_control.h"

static int uhid_fd;
void uhid_create(char *descriptor, int size)
{
	if (access("/dev/uhid", F_OK) != 0)
		return;
	uhid_fd = open("/dev/uhid", O_RDWR);
	if (uhid_fd > 0)
	{
		int ret;
		struct uhid_event event;

		event.type = UHID_CREATE2;
		strcpy((char*)event.u.create2.name, "uibc");
		strcpy((char*)event.u.create2.phys, "uibc_phys");
		strcpy((char*)event.u.create2.uniq, "uibc_uniq");
		event.u.create2.vendor = 0x1233;
		event.u.create2.product = 0x3455;
		event.u.create2.version = 0x5677;
		event.u.create2.rd_size = size;
		memcpy(event.u.create2.rd_data, descriptor, size);

		ret = write(uhid_fd, &event, sizeof(event));
		usleep(10000);
	}
}
void uhid_create_default(bool mouse_enable, bool keyboard_enable)
{
	int size = 0;
	char descriptor[4096];
	// 描述符在线分析: https://www.usbzh.com/tool/usb.html

	// hid附录 E.10
	char standard_mouse_descriptor[] = "\x05\x01\x09\x02\xa1\x01\x09\x01\xa1\x00" \
		"\x05\x09\x19\x01\x29\x03\x15\x00\x25\x01\x95\x03\x75\x01\x81\x02" \
		"\x95\x01\x75\x05\x81\x01\x05\x01\x09\x30\x09\x31\x15\x81\x25\x7f" \
		"\x75\x08\x95\x02\x81\x06\xc0\xc0";

	// hid附录 E.6
	char standard_keyboard_descriptor[] = "\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0" \
		"\x29\xe7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08" \
		"\x81\x01\x95\x05\x75\x01\x05\x08\x19\x01\x29\x05\x91\x02\x95\x01" \
		"\x75\x03\x91\x01\x95\x06\x75\x08\x15\x00\x25\x65\x05\x07\x19\x00" \
		"\x29\x65\x81\x00\xc0";

	// 带report_id的鼠标描述符
	char mouse_descriptor[] = "\x05\x01\x09\x02\xa1\x01\x85" \
		"\x28\x09\x01\xa1\x00\x05\x09\x19\x01\x29\x08\x15\x00\x25\x01\x95" \
		"\x08\x75\x01\x81\x02\x05\x01\x09\x30\x09\x31\x09\x38\x0a\x38\x02" \
		"\x15\x81\x25\x7f\x75\x08\x95\x04\x81\x06\xc0\xc0";

	// 带report_id的键盘描述符
	char keyboard_descriptor[] = "\x05\x01\x09\x06\xa1\x01\x85" \
		"\x29\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01\x75\x01\x95\x08\x81" \
		"\x02\x95\x01\x75\x08\x81\x03\x95\x05\x75\x01\x05\x08\x19\x01\x29" \
		"\x05\x91\x02\x95\x01\x75\x03\x91\x03\x95\x06\x75\x08\x15\x00\x25" \
		"\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0";


	// standard_mouse_descriptor 和 standard_keyboard_descriptor 不可同时使用,
	// 因为他们没有Report ID,无法区别输入数据是属于鼠标还是键盘
	if (mouse_enable)
	{
		memcpy(&descriptor[size], mouse_descriptor, sizeof(mouse_descriptor) - 1);
		size += sizeof(mouse_descriptor) - 1;
	}
	if (keyboard_enable)
	{
		memcpy(&descriptor[size], keyboard_descriptor, sizeof(keyboard_descriptor) - 1);
		size += sizeof(keyboard_descriptor) - 1;
	}

	if (size > 0)
		uhid_create(descriptor, size);
}
void uhid_send_report(const char *data, int size)
{
	int ret;
	struct uhid_event event;

	if (uhid_fd > 0 && size <= 4096)
	{
		memset(&event, 0, sizeof(event));
		event.type = UHID_INPUT2;
		event.u.input2.size = size;
		memcpy(event.u.input2.data, data, size);
		ret = write(uhid_fd, &event, sizeof(event));
	}
}
void uhid_send_default_mouse(bool left_btn, bool right_btn, bool middle_btn, int x, int y, int wheel)
{
	char report[5] = { 0x28 };

	report[1] += left_btn ? 0x01 : 0;
	report[1] += right_btn ? 0x02 : 0;
	report[1] += middle_btn ? 0x04 : 0;
	report[2] = x;
	report[3] = y;
	report[4] = wheel;
	uhid_send_report(report, 5);
}
void uhid_send_default_keyboard(struct Keyboard_Ext ext, int key[6])
{
	char report[10] = { 0x29 };

	report[1] += ext.LeftControl? 0x01 : 0;
	report[1] += ext.LeftShift	? 0x02 : 0;
	report[1] += ext.LeftAlt	? 0x04 : 0;
	report[1] += ext.Left_GUI	? 0x08 : 0;
	report[1] += ext.RightControl?0x10 : 0;
	report[1] += ext.RightShift ? 0x20 : 0;
	report[1] += ext.RightAlt	? 0x40 : 0;
	report[1] += ext.Right_GUI	? 0x80 : 0;
	report[4] = key[0];
	report[5] = key[1];
	report[6] = key[2];
	report[7] = key[3];
	report[8] = key[4];
	report[9] = key[5];
	uhid_send_report(report, 10);
}
void uhid_close(void)
{
	if (uhid_fd > 0)
		close(uhid_fd);
	uhid_fd = 0;
}
void uhid_test(void)
{
	struct Keyboard_Ext ext = {};
	int key[6] = {};

	// 创建虚拟鼠标键盘设备
	uhid_create_default(true, true);

	// 打开input子系统调试工具
	system("evtest /dev/input/event4&");	// 鼠标input设备,具体数字需根据实际进行修改
	system("evtest /dev/input/event5&");	// 键盘input设备,具体数字需根据实际进行修改
	usleep(500000);

	// 按下鼠标左键
	uhid_send_default_mouse(1, 0, 0, 10, 20, 30);
	usleep(100000);

	// 松开鼠标左键
	uhid_send_default_mouse(0, 0, 0, 0, 0, 0);
	usleep(100000);


	printf("\n\n\n");
	// 按下键盘上的a
	ext.LeftControl = 1;
	key[0] = 4;	// 具体的键值,需查询hid usage规范pdf
	uhid_send_default_keyboard(ext, key);
	usleep(100000);

	// 松开键盘上的a
	ext.LeftControl = 0;
	key[0] = 0;
	uhid_send_default_keyboard(ext, key);
	usleep(100000);

	uhid_close();
	system("killall evtest");
}


static int mt_fd;
static struct timeval time_start;
static struct MT_event mt_event[10];
void multi_touch_create(void)
{
	if (access("/dev/uhid", F_OK) != 0)
		return;
	mt_fd = open("/dev/uhid", O_RDWR);
	if (mt_fd > 0)
	{
		int ret;
		struct uhid_event event;

		event.type = UHID_CREATE2;
		strcpy((char*)event.u.create2.name, "uibc_mt");
		strcpy((char*)event.u.create2.phys, "uibc_mt_phys");
		strcpy((char*)event.u.create2.uniq, "uibc_mt_uniq");
		event.u.create2.vendor = 0x1230;
		event.u.create2.product = 0x3450;
		event.u.create2.version = 0x5670;

		// 十点触控描述符
		char multi_touch_descriptor[] =
			"\x05\x0D\x09\x04\xA1\x01\x85\x04\x09\x22\xA1\x02\x05\x0D\x95\x01" \
			"\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09\x42\x25\x01\x75\x01" \
			"\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05\x01\x75\x10\x55\x0E" \
			"\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75\x0D\x81\x42\x09\x31" \
			"\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22\xA1\x02\x05" \
			"\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09\x42\x25" \
			"\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05\x01\x75" \
			"\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75\x0D\x81" \
			"\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22" \
			"\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02" \
			"\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03" \
			"\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46" \
			"\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05" \
			"\x0D\x09\x22\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25" \
			"\x3F\x81\x02\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95" \
			"\x01\x81\x03\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E" \
			"\x35\x00\x46\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81" \
			"\x42\xC0\x05\x0D\x09\x22\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51" \
			"\x15\x00\x25\x3F\x81\x02\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02" \
			"\x75\x01\x95\x01\x81\x03\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30" \
			"\x26\x80\x1E\x35\x00\x46\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46" \
			"\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22\xA1\x02\x05\x0D\x95\x01\x75" \
			"\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09\x42\x25\x01\x75\x01\x95" \
			"\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05\x01\x75\x10\x55\x0E\x65" \
			"\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75\x0D\x81\x42\x09\x31\x26" \
			"\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22\xA1\x02\x05\x0D" \
			"\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09\x42\x25\x01" \
			"\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05\x01\x75\x10" \
			"\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75\x0D\x81\x42" \
			"\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22\xA1" \
			"\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09" \
			"\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05" \
			"\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75" \
			"\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D" \
			"\x09\x22\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F" \
			"\x81\x02\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01" \
			"\x81\x03\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35" \
			"\x00\x46\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42" \
			"\xC0\x05\x0D\x09\x22\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15" \
			"\x00\x25\x3F\x81\x02\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75" \
			"\x01\x95\x01\x81\x03\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26" \
			"\x80\x1E\x35\x00\x46\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A" \
			"\x07\x81\x42\xC0\x05\x0D\x09\x56\x55\x00\x65\x00\x27\xFF\xFF\xFF" \
			"\x7F\x95\x01\x75\x20\x81\x02\x09\x54\x25\x7F\x95\x01\x75\x08\x81" \
			"\x02\xC0";

		event.u.create2.rd_size = sizeof(multi_touch_descriptor) - 1;
		memcpy(event.u.create2.rd_data, multi_touch_descriptor, sizeof(multi_touch_descriptor) - 1);
		ret = write(mt_fd, &event, sizeof(event));
		usleep(10000);
	}
}
void multi_touch_set(struct MT_event *event, int count)
{
	int ret;
	long usec;
	char report[56] = { 0x04 };
	struct uhid_event uevent;

	for (int i = 0; i < 10 && i < count; i++)
	{
		unsigned char s;

		s = event[i].state ? 0x40 : 00;
		report[i * 5 + 1] = i + s;
		report[i * 5 + 2] = event[i].x & 0xff;
		report[i * 5 + 3] = (event[i].x >> 8) & 0xff;
		report[i * 5 + 4] = event[i].y & 0xff;
		report[i * 5 + 5] = (event[i].y >> 8) & 0xff;
	}

	if (time_start.tv_sec == 0 && time_start.tv_usec == 0)
	{
		gettimeofday(&time_start, NULL);
		usec = 1000000 * time_start.tv_sec + time_start.tv_usec;
		usec = 0;
	}
	else
	{
		struct timeval end;

		gettimeofday(&end, NULL);
		usec = 1000000 * (end.tv_sec - time_start.tv_sec) + (end.tv_usec - time_start.tv_usec);
	}

	usec = usec / 100;
	report[51] = usec & 0xff;
	report[52] = (usec >> 8) & 0xff;
	report[53] = (usec >> 16) & 0xff;
	report[54] = (usec >> 24) & 0xff;
	report[55] = count;

	memset(&uevent, 0, sizeof(event));
	uevent.type = UHID_INPUT2;
	uevent.u.input2.size = 56;
	memcpy(uevent.u.input2.data, report, 56);
	ret = write(mt_fd, &uevent, sizeof(uevent));
}
void multi_touch_close(void)
{
	if (mt_fd > 0)
		close(mt_fd);
	mt_fd = 0;
}
static void multi_touch_replay(void)
{
	char feature[] =
		"\x06\x0A\xFC\x28\xFE\x84\x40\xCB\x9A\x87\x0D\xBE\x57\x3C\xB6\x70" \
		"\x09\x88\x07\x97\x2D\x2B\xE3\x38\x34\xB6\x6C\xED\xB0\xF7\xE5\x9C" \
		"\xF6\xC2\x2E\x84\x1B\xE8\xB4\x51\x78\x43\x1F\x28\x4B\x7C\x2D\x53" \
		"\xAF\xFC\x47\x70\x1B\x59\x6F\x74\x43\xC4\xF3\x47\x18\x53\x1A\xA2" \
		"\xA1\x71\xC7\x95\x0E\x31\x55\x21\xD3\xB5\x1E\xE9\x0C\xBA\xEC\xB8" \
		"\x89\x19\x3E\xB3\xAF\x75\x81\x9D\x53\xB9\x41\x57\xF4\x6D\x39\x25" \
		"\x29\x7C\x87\xD9\xB4\x98\x45\x7D\xA7\x26\x9C\x65\x3B\x85\x68\x89" \
		"\xD7\x3B\xBD\xFF\x14\x67\xF2\x2B\xF0\x2A\x41\x54\xF0\xFD\x2C\x66" \
		"\x7C\xF8\xC0\x8F\x33\x13\x03\xF1\xD3\xC1\x0B\x89\xD9\x1B\x62\xCD" \
		"\x51\xB7\x80\xB8\xAF\x3A\x10\xC1\x8A\x5B\xE8\x8A\x56\xF0\x8C\xAA" \
		"\xFA\x35\xE9\x42\xC4\xD8\x55\xC3\x38\xCC\x2B\x53\x5C\x69\x52\xD5" \
		"\xC8\x73\x02\x38\x7C\x73\xB6\x41\xE7\xFF\x05\xD8\x2B\x79\x9A\xE2" \
		"\x34\x60\x8F\xA3\x32\x1F\x09\x78\x62\xBC\x80\xE3\x0F\xBD\x65\x20" \
		"\x08\x13\xC1\xE2\xEE\x53\x2D\x86\x7E\xA7\x5A\xC5\xD3\x7D\x98\xBE" \
		"\x31\x48\x1F\xFB\xDA\xAF\xA2\xA8\x6A\x89\xD6\xBF\xF2\xD3\x32\x2A" \
		"\x9A\xE4\xCF\x17\xB7\xB8\xF4\xE1\x33\x08\x24\x8B\xC4\x43\xA5\xE5" \
		"\x24\xC2";

	// 单个手指触摸按下
	char a1[] =
		"\x04\x40\xAC\x15\x0B\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
		"\x00\x00\x00\xD0\x5C\x00\x00\x01";

	// 单个手指触摸移动到另一个位置
	char a2[] =
		"\x04\x40\xAC\x15\x0B\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
		"\x00\x00\x00\x88\x5E\x00\x00\x01";

	// 单个手指松开
	char a3[] =
		"\x04\x00\xAC\x15\x0B\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
		"\x00\x00\x00\xE0\x5E\x00\x00\x01";

	multi_touch_create();
	system("evtest /dev/input/event4&");
	usleep(500000);	

	{
		int ret;
		struct uhid_event event;

		sleep(1);
		printf("\n\n");
		memset(&event, 0, sizeof(event));
		event.type = UHID_INPUT2;
		event.u.input2.size = sizeof(a1) - 1;
		memcpy(event.u.input2.data, a1, sizeof(a1) - 1);
		ret = write(mt_fd, &event, sizeof(event));
		printf("\n[write ret = %d]\n", ret);
	}

	{
		int ret;
		struct uhid_event event;

		sleep(1);
		printf("\n\n");
		memset(&event, 0, sizeof(event));
		event.type = UHID_INPUT2;
		event.u.input2.size = sizeof(a2) - 1;
		memcpy(event.u.input2.data, a2, sizeof(a2) - 1);
		ret = write(mt_fd, &event, sizeof(event));
		printf("\n[write ret = %d]\n", ret);
	}
	{
		int ret;
		struct uhid_event event;

		sleep(1);
		printf("\n\n");
		memset(&event, 0, sizeof(event));
		event.type = UHID_INPUT2;
		event.u.input2.size = sizeof(a3) - 1;
		memcpy(event.u.input2.data, a3, sizeof(a3) - 1);
		ret = write(mt_fd, &event, sizeof(event));
		printf("\n[write ret = %d]\n", ret);
	}

	multi_touch_close();
	system("killall evtest");
}
void multi_touch_test(void)
{
	multi_touch_create();
	system("evtest /dev/input/event4&");
	usleep(500000);

	memset(mt_event, 0, sizeof(mt_event));
	mt_event[0].state = 1;
	mt_event[0].x = 160;
	mt_event[0].y = 160;
	multi_touch_set(mt_event, 1);
	sleep(1);

	printf("\n\n");
	memset(mt_event, 0, sizeof(mt_event));
	mt_event[0].state = 1;
	mt_event[0].x = 7900;
	multi_touch_set(mt_event, 1);
	sleep(1);

	printf("\n\n");
	memset(mt_event, 0, sizeof(mt_event));
	mt_event[0].state = 1;
	mt_event[0].x = 480;
	multi_touch_set(mt_event, 1);
	sleep(1);

	printf("\n\n");
	memset(mt_event, 0, sizeof(mt_event));
	mt_event[0].state = 0;
	mt_event[0].x = 640;
	multi_touch_set(mt_event, 1);
	sleep(1);

	multi_touch_close();
	system("killall evtest");
}

int main(int argc, char **argv)
{
	if(1)
	{
		// 创建鼠标键盘
		uhid_create_default(true, true);

		// 延迟999s,用户这时可以用cat /proc/bus/input/devices查看,是否多出两个input设备
		sleep(999);

		// 删除鼠标键盘
		uhid_close();
	}
	else
	{
		// 创建并测试鼠标键盘
		uhid_test();
	}
	return 0;
}

测试结果

  1.执行cat /proc/bus/input/devices会发现多了这两个input设备 。

avd Android虚拟设备AVD_描述符

  2.创建并测试鼠标键盘时,应用程序会发送鼠标和键盘数据给hid,我们会发现input有数据上报。 图片中上半部分为鼠标事件,下半部分为键盘事件。

avd Android虚拟设备AVD_描述符_02