安卓设备监听全部输入信号

news2025/2/1 13:46:28

前言:

最近团队收到一个产品需求,需要监听安卓设备上用户是否有输入行为,以免定制推荐的时候打搅到用户。这里指的是设备上所有应用的输入行为,而不是单指某一个应用。

这个需求还是蛮有挑战性的,需要涉及到很多FW层的知识,所以围绕着这个需求,定制了多个方案,并且也找了许多人进行讨论,总算有了一个相对可行的方案,因此,通过本文记录一下,也分享给有同样需求的后来者。

这里先介绍一下大背景,我们是定制的设备,设备上有很多的APP,每个APP都是不同的团队来负责的。甚至于系统侧的代码和整体集成,也是不同的来团队负责的。

该需求高度依赖事件分发流程的原理,所以算是对事件分发流程的一个实践。

方案选择

方案1:APP集成SDK的方案

我们可以出一个SDK,让每个APP去集成。因为SDK是作用于APP中的,所以可以在SDK中,去注册相应的输入监听。

举个例子,事件分发流程中,Activity的事件分发都会走到Activity.onTouchEvent方法,方法如下:

public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }

    return false;
}

这里涉及到一个成员变量mWindow,而这个我们可以在监听到Activity启动后,通过hook的方式构建一个代理类hookWindow,替换调原有的mWindow,从而实现输入事件的监听。

这个方案技术来讲,实现难度比较低,可行性较高。但是从业务角度上,就需要上百个APP都接入这个SDK,就是近百个APP的接入成本,并且需要个十几个团队打交道去沟通,甚至有的团队还是外部的,所以这个方案从业务角度上,可行性是极低的,提出来后甚至没有和产品商量,直接被我们技术内部否决了。

方案2:改framework方案

事件分发流程中,大体流程如下图所示:

 

如图所示,InputDispatcher收到了输入信号之后,负责找到对应的window,然后再把输入事件分发给对应的window。所以,如果我们能在InputDispatcher中做一个钩子,每当有信号过来的时候,通过这个钩子向外界分发,我们就可以知道用户是否有输入的行为了。

这样做的优势就是不需要任何APP的修改,对接团队的数量大幅的降低了,而且纯看代码,好像要写的代码也不是很多,只需要在dispatchMotionLocked方法被调用的时候,向外界发出一个通知就可以了。

但是经过考虑,还是放弃了这个方案,原因有以下几个:

1.需要对FW层进行修改,而且是主流程的代码,一旦写的有问题,造成的后果将不可想象,甚至会导致用户所有的输入事件全部失效。

2.涉及到了多个团队,因为我们不同的设备的系统是不同的团队来负责的。需要他们配合才能修改,又涉及到了外部沟通。一旦沟通,那么排期,上线就是变的遥不可及,甚至于人家处于第一个原因根本不愿意配合去做。

方案3:监听底层输入源

如下图所示,整个事件分发流程中,最底层的来源是EventHub。

 

那么EventHub监听的是什么呢?既然EventHub可以监听,那么我们是否可以做一个同样的监听呢?

EventHub监听的其实是dev/input文件夹下的几个文件,比如event0,event1,event2这样,分别代表不同的输入来源。其实event0也不能称之为文件,他们其实属于驱动文件,并不能直接读的。

这样做的优势有如下几个:

1.不依赖外部团队。完全不依赖任何的外部团队,只要装了APP就可以生效。

2.安全性。因为不涉及到FW层修改,所以对系统的危险性大大降低。

3.可回退性。APP是可发版可热更新的,不像是FW层,一旦改坏了,就得重新刷ROM了。

所以总结了一下几个实现方案,3无疑是效果最好的,所以把实现方向,主要定位到第三个上。

可行性分析

如果我们直接通过adb命令获取event文件,发现是不可行的,说明这不是一个具体的文件。

adb pull dev/input/event0

如果我们通过FileObserver的方式添加监听,发现也是不可行的,会提示错误:

  I  type=1400 audit(0.0:2293): avc: denied { read } for name="event0" dev="tmpfs" ino=540 scontext=u:r:system_app:s0 tcontext=u:object_r:input_device:s0 tclass=chr_file permissive=1

所以,最简单的两个监听方案,很自然是走不通的。

接下来,我们通过ps看一下system_server进程,发现其只是一个system级别的应用,而我们也是一个system级别的应用,所以既然system_server可以读取event信号,那么我们理论上也是可以的。

所以,我们就需要EventHub中的代码,来看看EventHub是怎么取值的,我们可以参考着其中的实现而实现我们的需求。

EventHub实现原理

我们首先看一下EventHub创建时的代码:

EventHub::EventHub(void){
    mEpollFd = epoll_create1(EPOLL_CLOEXEC);
    mINotifyFd = inotify_init1(IN_CLOEXEC);
    struct epoll_event eventItem = {};
    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = mINotifyFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    ...
}

构造函数中,注册了一个mINotifyFd,然后通过epoll_ctl绑定添加事件,也就是说如果mINotifyFd如果新的添加事件时,会通过mEpollFd向其注册者发送信号,并且携带eventItem对象。

那么就会有两个问题:

1.mINotifyFd绑定的是哪个文件?

2.epoll被唤醒后,通知的是谁?

第一个问题,答案在addDeviceInputInotify方法中,这个方法中,绑定了DEVICE_INPUT_PATH目录,也就是说,DEVICE_INPUT_PATH目录中,如果有文件添加或者删除,则会发出通知。

而这里的DEVICE_INPUT_PATH="/dev/input"。

void EventHub::addDeviceInputInotify() {
    mDeviceInputWd = inotify_add_watch(mINotifyFd, DEVICE_INPUT_PATH, IN_DELETE | IN_CREATE);
}

第二个问题,答案在getEvents方法中

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ...
    int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    ...
}

epoll_wait被唤醒后,传递过来的epoll_event对象将会被添加到mPendingEventItems集合中。接下来,我们就可以遍历mPendingEventItems集合进行依次处理了。

搜寻资料,得知inotify是用来监视文件系统事件的机制,当有事件发生时,inotify文件描述符会可读。我猜测这也就是为什么之前我们直接监听文件失败的原因(很遗憾,猜错了)。

 

实施方案

所以,参考EventHub中的实现,我们就可以完成我们的需求了。

我们可以也注册一个inotify,然后通过inotify_add_watch添加观察文件目录,也观察"/dev/input"文件夹。然后通过epoll_ctl绑定监听,当有事件输入时进行唤醒,唤醒后读取mINotifyFd描述符中的文件内容。

其实,因为我们的需求只是观察用户是否有输入行为,而不是观察用户输入了什么,所以我们深知都不需要解析mINotifyFd描述符中的内容,只需要发生了,就认为有输入。

分为两个方法,方法createListenerInput主要用于创建native层对象,并且初始化相关的成员变量,以及开启监听。

方法readLastInputTime则负责读取native对象中的最近输入时间这个属性值。

createListenerInput方法相关的实现代码如下:

void ListenerInput::registerWatchInputTime() {
    LOGI("%s%d", "registerWatchInputTime,mINotifyFd:", mINotifyFd);
    int mDeviceInputWd = inotify_add_watch(mINotifyFd, DEVICE_INPUT_PATH, IN_DELETE | IN_CREATE);
    LOGI("%s%d", "registerWatchInputTime,mDeviceInputWd:", mDeviceInputWd);

    struct epoll_event eventItem = {};
    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = mINotifyFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    LOGI("%s%d", "epoll_ctl.result:", result);
    startThread();
}

void ListenerInput::listenerInput() {
    for (;;) {
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, 16, 10000L);
        LOGI("%s%d", "epoll_wait.pollResult:", pollResult);
        if (pollResult == 0) {
            // Timed out.
            break;
        }
    }
}

void ListenerInput::startThread() {
    std::thread myThread(&ListenerInput::listenerInput, this);
    myThread.detach();
}

JNIEXPORT jlong JNICALL
Java_com_beantechs_watchinput_WatchInput_createListenerInput(JNIEnv *env, jobject instance) {
    LOGI("%s", "Java_com_beantechs_watchinput_WatchInput_createListenerInput start");
    ListenerInput *listenerInput = new ListenerInput();
    listenerInput->registerWatchInputTime();
    LOGI("%s", "Java_com_beantechs_watchinput_WatchInput_createListenerInput end");
    return reinterpret_cast<long>(listenerInput);
}

readLastInputTime方法相关的实现代码如下:

long ListenerInput::readLastInputTime() {
    LOGI("%s%ld", "readLastInputTime",mLastInputTime);
    return mLastInputTime;
}

JNIEXPORT jlong JNICALL
Java_com_beantechs_watchinput_WatchInput_readLastInputTime(JNIEnv *env, jobject instance,
                                                           jlong ptr) {
    LOGI("%s", "Java_com_beantechs_watchinput_WatchInput_readLastInputTime start");
    LOGI("%s%lld", "ptr:", ptr);
    long nativeLongValue = static_cast<long>(ptr);
    ListenerInput *listenerInput = reinterpret_cast<ListenerInput *>(nativeLongValue);
    long inputTime = listenerInput->readLastInputTime();
    LOGI("%s%ld", "Java_com_beantechs_watchinput_WatchInput_readLastInputTime end,inputTime:",
         inputTime);
    return 1l;
}

但是实际运行的时候,发现又被权限管理给限制掉了,提示错误:

type=1400 audit(0.0:7273): avc: denied { read } for name="input" dev="tmpfs" ino=10275 scontext=u:r:system_app:s0 tcontext=u:object_r:input_device:s0 tclass=dir permissive=1

哪怕关掉了SElinux,仍然提示同样的错误。

inputflinger归属system_server进程,而system_server进程属于system级别的应用。而我的应用也是system级别的,所以为什么system_server可以,我的应用不行,这块的原因还未找到,还处于排查中。

声明

本技术方案仅供参考,严禁用于任何非法目的商业活动。

本方案只是一个方向性的探索,并没有真正的去实现,最终是否能够实现也是一个未知数。这篇文章只是做一个初步的分享,当然欢迎有类似方向或者需求的人一起讨论或者给予指引。

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

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

相关文章

Java设计模式之模板模式

1. 模板模式介绍 1、模板模式即模板方法模式自定义了一个操作中的算法骨架&#xff0c;而将步骤延迟到子类中&#xff0c;使得子类可以不改变一个算法的结构&#xff0c;可以自定义该算法的某些特定步骤&#xff1b; 2、父类中提取了公共的部分代码&#xff0c;便于代码复用&am…

PyTorch深度学习实战(5)——计算机视觉基础

PyTorch深度学习实战&#xff08;5&#xff09;——计算机视觉基础 0. 前言1. 图像表示2. 将图像转换为结构化数组2.1 灰度图像表示2.2 彩色图像表示 3 利用神经网络进行图像分析的优势小结系列链接 0. 前言 计算机视觉是指通过计算机系统对图像和视频进行处理和分析&#xff…

Scala(二)

第2章 变量和数据类型 2.1 注释 Scala注释使用和Java完全一样。 注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来&#xff0c;再用代码去体现。 1&#xff09;基本语法 &#xff08;1&#xff09;单行注释&#xff1a;// &#xff08;2&#xff0…

高时空分辨率、高精度一体化预测技术的风、光、水自动化预测技术的应用

第一章 预测平台讲解及安装 一、高精度气象预测基础理论介绍 综合气象观测数值模拟模式&#xff1b; 全球预测模式、中尺度数值模式&#xff1b; 二、自动化预测平台介绍 Linux系统 Crontab定时任务执行机制 Bash脚本自动化编程 硬件需求简介 软件系统安装 …

独立站该怎么带来客户流量?来看看这五大方法吧!

建立独立站是为了让更多的人知道你的品牌和产品&#xff0c;从而吸引潜在客户并转化为销售机会。以下是一些可以帮助独立站带来客户流量的方法&#xff1a; 01.SEO&#xff08;搜索引擎优化&#xff09;&#xff1a; 优化网站的SEO&#xff0c;使得搜索引擎能够更好地找到你的…

MyBatis之慎用association

这里先总结一下 association 存在的问题。 一、内嵌查询时存在报错Id找不到及内存溢出隐患 二、一对多关系数据重复问题 三、多层嵌套内层 association 查询结果为null 或 非预期的值 一、内嵌查询时存在报错Id找不到及内存溢出隐患 参考&#xff1a; https://www.lmlphp.co…

DP358/321/323/324运算放大器芯片

DP358、DP321、DP323、DP324是一款低噪声、低压、低 功耗轨到轨输出运放大器&#xff0c;该系列放大器的增益带宽为 11MHz,压摆率为 8.5V/uS,其中DP323 在掉电工作模式下待机电流小于1uA。该系列放大器可以广泛应用于各种电子产品领域。 主要特性&#xff1a; 轨到轨最大输入…

电脑提示msvcr110.dll丢失怎样修复呢?推荐三个修复方法

Windows系统总是不可避免会出现系统报错&#xff0c;提示msvcr110.dll丢失&#xff0c;无法运行启动软件程序&#xff0c;主要就是由于系统的msvcr110.dll丢失或者损坏。msvcr110.dll是Microsoft Visual C Redistributable软件包中的一个文件&#xff0c;它是由Microsoft Visua…

【文生图系列】Stable Diffusion Webui安装部署过程中bug汇总(Linux系统)

文章目录 bugs虚拟环境pythongfpgan和cython bugs 看网上部署stable diffusion webui的教程&#xff0c;很简单。而且我也有部署stable diffusion v1/v2的经验&#xff0c;想着应该会很快部署完stable diffusion webui&#xff0c;但是没想到在部署过程中&#xff0c;遇到各种…

【数据分析 - 基础入门之pandas篇③】- pandas数据结构——DataFrame

文章目录 前言一、DataFrame创建1.1 字典创建1.2 NumPy二维数组创建 二、DataFrame切片2.1 行切片2.2 列切片2.3 行列切片 三、DataFrame运算3.1 DataFrame和标量的运算3.2 DataFrame之间的运算3.3 Series和DataFrame之间的运算 四、DataFrame多层次索引4.1 多层次索引构造1.隐…

AJAX异步请求JSON数据格式

目录 前言 1.AJAX的实现方式 1.1原生的js实现方式 1.2JQuery实现方式 2.1语法 3.JSON数据和Java对象的相互转换 3.1将JSON转换为Java对象 3.2将Java对象转换为JSON 前言 AJAX&#xff1a;ASynchronous JavaScript And XML 异步的JavaScript 和 XML。 Ajax 是一种在…

在安卓里用c++显示骨骼动画

1. 程序模块图 2. 编译第三方库Assimp 2.1 下载 官网下载5.0.0版本,https://codeload.github.com/assimp/assimp/zip/refs/tags/v5.0.0 2.2 生成安卓编译链 解压后在assimp-5.0.0下建文件夹BuildAssimp 放两个脚本make_standalone_toolchain.bat python D:/Android/Sdk/nd…

安达发|各部门实施APS系统前后有哪些变化?

众所周知&#xff0c;生产计划部门是制造企业的重要部门&#xff0c;承担销售、采购、仓储、质量检验和生产的各个部门的协调工作。APS 先进计划排程系统系统通过人工智能算法跟踪所有资源&#xff0c;包括材料、设备、人员、客户需求、订单变更等&#xff0c;自动快速计算出“…

odoo-031 odoo13和odoo16的网站上添加显示变体描述 Website Add Variant Description

文章目录 测试环境需求描述实现步骤实际效果思路说明 测试环境 Odoo 版本&#xff1a; odoo13 和 odoo16 Python 版本&#xff1a;3.6.9 操作系统&#xff1a;Ubuntu 18.04 需求描述 添加变体描述&#xff0c;显示在 form 视图&#xff1b;在网站上动态显示产品变体描述。 …

QT之智能指针

如果没有智能指针&#xff0c;程序员必须保证new对象能在正确的时机delete&#xff0c;四处编写异常捕获代码以释放资源&#xff0c;而智能指针则可以在退出作用域时(不管是正常流程离开或是因异常离开)总调用delete来析构在堆上动态分配的对象。 来看看一个野指针例子 程序将会…

在 3ds Max 中创建逼真的玻璃材质

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 尽管本教程基于 3ds Max&#xff0c;但相同的设置适用于许多其他 3D 产品。 注意&#xff1a;单击每个步骤中的缩略图可查看更大的屏幕截图&#xff0c;其中包括视口和用户界面的相关部分。 步骤 1由于本教…

Linux的权限管理精细总结

&#xff08;该图由AI绘制 关注我 学习AI画图&#xff09; 目录 一、权限概述 1、权限的基本概念 2、为什么要设置权限 3、Linux用户身份类别 4、user文件拥有者 5、group文件所属组内用户 6、other其他用户 7、特殊用户root 二、普通权限管理 1、ls -l命令查看文件…

LED显示屏的8个常见信号干扰因素及解决方法

LED显示屏在使用过程中可能会受到多种信号干扰因素的影响&#xff0c;导致显示效果不理想或出现问题。以下是LED显示屏常见的信号干扰因素以及对应的解决方法&#xff1a; 1&#xff0c;电源干扰&#xff1a; 干扰因素&#xff1a;电源波动、电源噪声等。 解决方法&#xff1a…

jenkins发布使用邮件添加审批

首先安装好Email Extension Plugin插件并在 system下配置好邮件 然后配置流水线需要的参数 ![在这里插入图片描述](https://img-blog.csdnimg.cn/418fc89bfa89429783a1eb37d3e4ee26.png#pic_center pipeline如下&#xff1a; def skipRemainingStages false //是否跳过生…

【原创】实现GPT中Transformer模型之框架概念

作者&#xff1a;黑夜路人 时间&#xff1a;2023年7月 GPT是什么意思 GPT的全称是 Generative Pre-trained Transformer&#xff08;生成型预训练变换模型&#xff09;&#xff0c;它是基于大量语料数据上训练&#xff0c;以生成类似于人类自然语言的文本。其名称中的“预训练”…