Andorid Input事件 注入方法及原理介绍

news2024/12/23 14:39:17

在Android系统中,除了真实的输入设备可以产生事件之外,我们也可以通过软件的方式,模拟一个输入事件,比如模拟一个点击事件,模拟一个按键事件等等。

怎么模拟一个输入事件
1,在adb命令行使用input命令模拟输入事件
源码路径:frameworks\base\cmds\input\src\com\android\commands\input\Input.java

@Override
    public void onShowUsage(PrintStream out) {
        out.println("Usage: input [<source>] [-d DISPLAY_ID] <command> [<arg>...]");
        out.println();
        out.println("The sources are: ");
        for (String src : SOURCES.keySet()) {
            out.println("      " + src);
        }
        out.println();
        out.printf("-d: specify the display ID.\n"
                + "      (Default: %d for key event, %d for motion event if not specified.)",
                INVALID_DISPLAY, DEFAULT_DISPLAY);
        out.println();
        out.println("The commands and default sources are:");
        out.println("      text <string> (Default: touchscreen)");
        out.println("      keyevent [--longpress] <key code number or name> ..."
                + " (Default: keyboard)");
        out.println("      tap <x> <y> (Default: touchscreen)");
        out.println("      swipe <x1> <y1> <x2> <y2> [duration(ms)]"
                + " (Default: touchscreen)");
        out.println("      draganddrop <x1> <y1> <x2> <y2> [duration(ms)]"
                + " (Default: touchscreen)");
        out.println("      press (Default: trackball)");
        out.println("      roll <dx> <dy> (Default: trackball)");
        out.println("      motionevent <DOWN|UP|MOVE> <x> <y> (Default: touchscreen)");
    }

从使用方法说明中可以看出,使用input keyevent 可以模拟一个按键事件,使用input tap 可以模拟一个点击事件。他们都是通过调用IMS的injectInputEvent方法向系统注入事件,只是传入的event类型不一致

InputManager.getInstance().injectInputEvent(event,InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);

2,在代码中模拟输入事件

对于使用系统源码编译的APK,当然可以直接调用IMS的injectInputEvent来模拟一个输入事件。
如果APP不是放在系统源码中编译,直接使用这个方法的话,肯定是编译不过的,这时候我们可以借助Instrumentation类来实现模拟一个输入事件,例如注入一个MotionEvent 的down事件

Instrumentation instrumentation = new Instrumentation();
MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 100, 500, 			   0.0f, 1.0f, 0, 1.0f, 1.0f,getInputDeviceId(InputDevice.SOURCE_TOUCHSCREEN), 0);
instrumentation.sendPointerSync(event);

//
private static int getInputDeviceId(int inputSource) {
        final int DEFAULT_DEVICE_ID = 0;
        int[] devIds = InputDevice.getDeviceIds();
        for (int devId : devIds) {
            InputDevice inputDev = InputDevice.getDevice(devId);
            if (inputDev.supportsSource(inputSource)) {
                return devId;
            }
        }
        return DEFAULT_DEVICE_ID;
    }

3,对于自己应用内的事件注入,是有权限的,但是如果想注入事件到其它的应用,是需要申请INJECT_EVENTS权限的,而这个权限是需要系统签名的应用才能申请

 <permission android:name="android.permission.INJECT_EVENTS"
        android:protectionLevel="signature" />

所以,应用需要有系统签名,并且申请了INJECT_EVENTS权限,才能注入事件到其它的应用

注入事件的原理分析

不管上面使用哪种方式实现的事件注入,最终都是调用IMS的injectInputEvent方法,injectInputEvent方法中直接调用injectInputEventInternal,从这个方法开始分析

//frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
private boolean injectInputEventInternal(InputEvent event, int mode) {
        if (event == null) {
            throw new IllegalArgumentException("event must not be null");
        }
        if (mode != InputManager.INJECT_INPUT_EVENT_MODE_ASYNC
                && mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
                && mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
            throw new IllegalArgumentException("mode is invalid");
        }

        final int pid = Binder.getCallingPid();//记录调用者的pid
        final int uid = Binder.getCallingUid();//记录调用者的uid
        final long ident = Binder.clearCallingIdentity();
        final int result;
        try {
            result = nativeInjectInputEvent(mPtr, event, pid, uid, mode,
                    INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
       //省略
    }

调用Native方法,需要传入调用者的pid和uid,因为后续需要判断调用者是否有注入事件的权限。

//frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobject inputEventObj, jint injectorPid, jint injectorUid,
        jint syncMode, jint timeoutMillis, jint policyFlags) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
        KeyEvent keyEvent;
        status_t status = android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
        if (status) {
            jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
            return INPUT_EVENT_INJECTION_FAILED;
        }

        const int32_t result =
                im->getInputManager()->getDispatcher()->injectInputEvent(&keyEvent, injectorPid,
                                                                         injectorUid, syncMode,
                                                                         std::chrono::milliseconds(
                                                                                 timeoutMillis),
                                                                         uint32_t(policyFlags));
        return static_cast<jint>(result);
    } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
        const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj);
        if (!motionEvent) {
            jniThrowRuntimeException(env, "Could not read contents of MotionEvent object.");
            return INPUT_EVENT_INJECTION_FAILED;
        }

        const int32_t result =
                (jint)im->getInputManager()
                        ->getDispatcher()
                        ->injectInputEvent(motionEvent, injectorPid, injectorUid, syncMode,
                                           std::chrono::milliseconds(timeoutMillis),
                                           uint32_t(policyFlags));
        return static_cast<jint>(result);
    } else {
        jniThrowRunti

对于按键事件以及触摸事件,都是调用InputDispatcher的injectInputEvent方法

int32_t InputDispatcher::injectInputEvent(const InputEvent* event, int32_t injectorPid,
                                          int32_t injectorUid, int32_t syncMode,
                                          std::chrono::milliseconds timeout, uint32_t policyFlags) {
   	//省略
	switch (event->getType()) {
        case AINPUT_EVENT_TYPE_KEY: {
        	//省略
			KeyEvent keyEvent;
            keyEvent.initialize(incomingKey.getId(), VIRTUAL_KEYBOARD_ID, incomingKey.getSource(),
                                incomingKey.getDisplayId(), INVALID_HMAC, action, flags, keyCode,
                                incomingKey.getScanCode(), metaState, incomingKey.getRepeatCount(),
                                incomingKey.getDownTime(), incomingKey.getEventTime());
            mLock.lock();
            KeyEntry* injectedEntry =
           	new KeyEntry(incomingKey.getId(), incomingKey.getEventTime(),
                                 VIRTUAL_KEYBOARD_ID, incomingKey.getSource(),
                                 incomingKey.getDisplayId(), policyFlags, action, flags, keyCode,
                                 incomingKey.getScanCode(), metaState, incomingKey.getRepeatCount(),
                                 incomingKey.getDownTime());//构造KeyEntry
            injectedEntries.push(injectedEntry);//放入injectedEntries
             break;
		}
		case AINPUT_EVENT_TYPE_MOTION: {
			//省略
			MotionEntry* injectedEntry =
                    new MotionEntry(motionEvent->getId(), *sampleEventTimes, VIRTUAL_KEYBOARD_ID,
                                    motionEvent->getSource(), motionEvent->getDisplayId(),
                                    policyFlags, action, actionButton, motionEvent->getFlags(),
                                    motionEvent->getMetaState(), motionEvent->getButtonState(),
                                    motionEvent->getClassification(), motionEvent->getEdgeFlags(),
                                    motionEvent->getXPrecision(), motionEvent->getYPrecision(),
                                    motionEvent->getRawXCursorPosition(),
                                    motionEvent->getRawYCursorPosition(),
                                    motionEvent->getDownTime(), uint32_t(pointerCount),
                                    pointerProperties, samplePointerCoords,
                                    motionEvent->getXOffset(), motionEvent->getYOffset());
            injectedEntries.push(injectedEntry);
            //省略
		}
		 bool needWake = false;
    while (!injectedEntries.empty()) {
        needWake |= enqueueInboundEventLocked(injectedEntries.front());//放入队列
        injectedEntries.pop();
    }

    mLock.unlock();

    if (needWake) {
        mLooper->wake();//唤醒InputDispatcher线程
    }

}

可以看出,注入事件也是放入iq队列。

总结
使用软件模拟注入事件也是放入iq队列,只是不需要经过InputReader的处理。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1677310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Milvus的系统架构

简介 Milvus的构建在许多知名的向量搜索库比如Faiss, HNSW, DiskANN, SCANN等之上的&#xff0c;它针对稠密向量数据集的相似搜索而设计&#xff0c;能支持百万、十亿甚至万亿级别的向量搜索。 Milvus支持数据分片&#xff0c;流式数据插入&#xff0c;动态schema&#xff0c…

仿C#或Java基础类型自定义

class Int{ private:int _value 0; public:operator int() const{ // 隐式转换return _value;}// 显式转换explicit operator int*() const { return nullptr; }operator(const int page){_value page;}operator float() const{return static_cast<float>(_value);}ope…

Linux shell编程学习笔记49:strings命令

0 前言 在使用Linux的过程中&#xff0c;有时我们需要在obj文件或二进制文件中查找可打印的字符串&#xff0c;那么可以strings命令。 1. strings命令 的功能、格式和选项说明 我们可以使用命令 strings --help 来查看strings命令的帮助信息。 pupleEndurer bash ~ $ strin…

Node.js安装及环境配置(超详细!保姆级!!)

目录 一、进入官网地址下载安装包 二、安装程序 三、环境配置 四、测试 五、安装淘宝镜像 一、进入官网地址下载安装包 Node.js — Download Node.js (nodejs.org) 选择对应你系统的 node.js 版本&#xff0c;我选择的是Windows系统&#xff0c;64位 点击图中选项&#…

图文详解JUC:Wait与Sleep的区别与细节

目录 一.Wait() 二.Sleep() 三.总结Wait()与Sleep()的区别 一.Wait() 在Java中&#xff0c;wait() 方法是 Object类中的一个方法&#xff0c;用于线程间的协作。当一个线程调用wait() 方法时&#xff0c;它会释放对象的锁并进入等待状态&#xff0c;直到其他线程调用相同对…

【氮化镓】p-GaN 栅 HEMT栅极可靠性及其退化机制

文章作者团队来自中国科学院苏州纳米技术与纳米仿生研究所&#xff08;SINANO&#xff09;的关键实验室、中国科学技术大学的纳米技术与纳米仿生学院&#xff0c;以及广东省&#xff08;佛山&#xff09;苏州纳米技术与纳米仿生研究所的分支机构。研究结果发表在IEEE Journal o…

详解lighthouse通过命令行方式运行并生成html测试报告的方法

lighthouse可以通过命令行的方式运行并生成html报告&#xff0c;我们可以通过lighthouse --help 命令查看命令行的详细用法&#xff0c;在这里我仅列出最常用的命令行使用方法&#xff01; 常用lighthouse命令行参数详解 * --chrome-flags&#xff1a;传递自定义标志给Chrome…

网络安全法中关于网络信息的保护和监管,有哪些规定?

网络安全法作为我们数字时代的重要法律保障&#xff0c;对于网络信息的保护和监管有着明确且详细的规定。这些规定不仅体现了国家对于网络安全的重视&#xff0c;也为我们每个人在数字世界中提供了坚实的法律屏障。 首先&#xff0c;我们来看一个关于网络运营者主体责任的案例。…

摸鱼大数据——Linux搭建大数据环境(集群免密码登录和安装Hadoop)二

集群设置免密登录 克隆node1虚拟机的前置条件&#xff1a;node1虚拟机存在且处于关闭状态 1.克隆出node2虚拟机 1.node1虚拟机: 右键 -> "管理" -> "克隆" 2.图形化弹窗中: "下一页"->"下一页"->选择"创建完整克隆&…

Element Plus组件库使用组件自动导入后样式不生效的问题

首先按照官方文档上的介绍进行配置&#xff1a;快速开始 | Element Plus (element-plus.org) 配置完成后&#xff0c;去组件中去测试组件库中的button组件的样式是否生效 <template><el-button type"primary">Primary</el-button> </template&…

windows和linux下分别安装pwntools

inux下安装pwntools pwntools是pwn最常用的一个python包。 首先需要安装pip&#xff1a;apt install python3-pip 然后安装pwntools&#xff1a;pip install pwntools 完成 10-1windows下安装pwntools 首先可以先安装好python的环境&#xff0c;Python 2.7.9 或 Python 3.4…

期权(1):基本概念,权利金,定金,买方,卖方,零和游戏,对赌协议

期权是合约&#xff0c;权利金就是定金&#xff01; 合约到期时 买方可以选择行权&#xff0c;也可以选择不行权。代价就是定金损失。因此亏损封顶&#xff0c;但盈利无限。卖方赚的就是买方的定金&#xff0c;盈利封顶&#xff0c;但亏损无限。 从这里&#xff0c;我们看出…

5.9网络协议

由网卡发送数据通过网线进行发送&#xff0c;当网卡接收到信号以后将数据传给内核数据区&#xff0c;然后由操作系统交给相应的进程。 将数据进行发送的时候需要借助于网线实现&#xff0c;这个时候会出现当传输的数据比较远的时候就借助于中继器将信号进行再生扩大&#xff0…

python如何做一个服务器fastapi 和flask

用 fastapi 方式的话 from fastapi import FastAPIapp FastAPI()app.get("/api") def index():return "hello world"然后需要安装 uvicorn 并执行下面的命令 uvicorn server:app --port 8000 --reload最终 如果是用 flask 直接写下面的代码 # -*- cod…

FullCalendar日历组件集成实战(4)

背景 有一些应用系统或应用功能&#xff0c;如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件&#xff0c;但功能比较简单&#xff0c;用来做数据展现勉强可用。但如果需要进行复杂的数据展示&#xff0c;以及互动操作如通过点击添加事件&#xff0…

Python GUI开发- PyQt5 开发小工具环境入门

前言 常见的python开发gui的库有 Tkinter&#xff0c; PyQt5&#xff0c; wxPython等。本教程是选择PyQt5 开发桌面小工具。 环境准备 只需pip安装即可快速准备好开发环境 pip install pyqt5快速开始 创建一个空的window窗口 Qapplication()&#xff1a;每个GUI都必须包含…

图片转base64【Vue + 纯Html】

1.template <el-form-item label"图片"><div class"image-upload-container"><input type"file" id"imageUpload" class"image-upload" change"convertToBase64" /><label for"imageU…

LabVIEW静止无功补偿监控系统

LabVIEW静止无功补偿监控系统 随着电力系统和电力电子技术的快速发展&#xff0c;静止无功补偿器作为提高电网质量和稳定性的关键设备&#xff0c;其监控系统的研发显得非常重要。详细介绍基于LabVIEW的SVC监控系统的设计与实现过程&#xff0c;可为电力系统的优化和电力电子技…

电机控制系列模块解析(21)—— 弱磁控制

一、弱磁控制 常用的FW即弱磁控制方法一般为&#xff1a;电压外环控制、单个电流环控制、直接输出电压幅值分配控制、输出电压角度PI控制、不弱磁控制、直接解析解、查表、速度反比例曲线拟合等等。 弱磁控制相关因素&#xff1a;过调制&#xff08;母线电压的剧烈波动&#x…

(十)Python基础练习题一(50道选择题)#Python

本文整理了Python基础知识相关的练习题&#xff0c;共50道&#xff0c;适用于刚入门初级Python想巩固基础的同学。来源&#xff1a;如荷学数据科学题库&#xff08;技术专项-Python一&#xff09;。 1&#xff09; 2&#xff09; 3&#xff09; 4&#xff09; 5&#xff09; 6…