Android 14输入系统架构分析:图解源码从驱动层到应用层的完整传递链路

一、资料快车

1、深入了解Android输入系统:https://blog.csdn.net/innost/article/details/47660387

2、书籍 - Android系统源代码情景分析

二、Perface

1、参考:

2、系统程序分析方法

1)加入log,并跟着log一步步分析 -logcat;

2)利用ChatGPT提供基础概念解析 & 代码解析 & 设计原理;

3、目标

1)提供查阅代码的线索、思路;

2)能够根据日志进行快读的代码分析;

3)区分代码层次,为定制系统提供思路;

4)站在前人的肩膀上进一步探究;

5)熟悉架构后,我们应能 如何添加自定义按键、拦截keyevent并添加自己的处理逻辑;

6)Android源代码兼容多种设备,比如一开始Android针对手机设计(因此很多代码都有手机的身影),后面适应不同的设备,不同设备的处理流程不同,注意分辨;

4、action

务必根据本文提供的线索 去看源代码。

带着疑问去了解

1、kl/kcm作用是什么?在哪里会被读取?

2、定义CVTE快捷键,应该怎么做?

3、如何跟中控和TVAPI联合起来?

三、代码目录

所在层次 名称 代码路径
Application 应用(调用系统库,实现用户逻辑) 实现android中对应的类及方法,进行inputevent处理
framework android (此部分链接到应用) /android/frameworks/base/services/java/com/android/server/SystemServer.java /android/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java /android/frameworks/base/core/java/* /android/frameworks/base/core/java/com/android/internal/policy/DecorView.java /android/frameworks/base/core/java/android/app/Activity.java /android/frameworks/base/core/java/android/view/ViewRootImpl.java /android/frameworks/base/core/java/android/view/View.java /android/frameworks/base/core/java/android/view/KeyEvent.java /android/frameworks/base/core/java/android/view/InputChannel.java
service (系统服务,服务端集中管理) Framework java基础服务 /android/frameworks/base/services/java/com/android/server/SystemServer.java Framework android核心服务 /android/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
JNI(过渡到native) 一、libservices.core - 静态库模块 /android/frameworks/base/services/core/jni//android/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp 二、libandroid_runtime - 静态库模块 /android/frameworks/base/core/jni/ /android/frameworks/base/core/jni/android_view_InputEventReceiver.cpp
NDK 一、libinput - 静态库模块 /android/frameworks/native/libs/input//android/frameworks/native/libs/input/InputTransport.cpp 二、libinputflinger/libinputflinger_base - 动态库模块 /android/frameworks/native/services/inputflinger/ /android/frameworks/native/services/inputflinger/InputManager.cpp 三、lib /android/system/core/libutils/* /android/system/core/libutils/Looper.cpp
cvte /android/frameworks/base/core/java/com/cvte/key/*
kernel input子系统

四、系统框架流程

1、粗略框图 - 来自深入了解Android

2、代码层级

3、framework部分

4、下面针对各个部分进行拆解分析

五、基础概念

1、APP界面构成关系

1)Application 、Activity、Windows 、Decro(ration) 、View

一个Application有多个Activity,Activity中的界面部分为Windows(PhoneWindow),是我们所看到的当前整个界面,windows界面由Decro(样式) 来描述,Decro由各种view组件(TextView、button)组成。

2)从代码角度看Decro与View的树状关系

3)小结:

android里:

1个application, 有1个或多个activity

1个activity, 有1个PhoneWindow

1个window, 有1个decor

1个decor, 有多个viewgroup/layout

viewgroup/layout中, 有多个view

2、Activity组件与ViewRoot对象的关系

Android设计思想 - 抽象分工

一个应用程序窗口是由一个Activity来描述(描述控件组成、布局等等)- 策略者;

每一个Activity组件都有一个关联的ViewRoot对象(除了绘制具体的窗口,还负责分发键盘事件) - 具体实施者;

Activity和ViewRoot通过DecorView和PhoneWindow对象来关联起来;

需要跟踪代码才能掌握理解以上关系

Android-Activity与Window与View之间的关系:https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode/blob/master/article/android/framework/Android-Activity与Window与View之间的关系.md

2、输入事件

输入事件有多种类型(触摸、拖动、鼠标、按键等等)

3、key相关概念

1)*.kl : keylayout,linux的scancode对应的Android按键映射;

2)*.kcm:key code map,Android按键对应的字符映射;

3)*.idc : input device configutation 输入设备配置文件;

以上文件在板卡都可以找到

4、socketpair 与 binder

socketpair 与 binder 为Android的跨进程通信技术模块,两者结合可以实现双向的跨进程通信。

5、inotify和epoll

inotify模块:用于监测某目录下的文件变化

epoll模块:用于监测文件内容变化

六、Linux kernel

1、input输入子系统

2、IR驱动

https://blog.csdn.net/STCNXPARM/article/details/134235394

七、Andorid系统部分

1、WMS

2、InputManagerService(java)

java层的InputManagerService对native层的InputManager进一步封装,很多实现都在native层。

1)Reader(CPP)

1、Reader角色
Reader主要工作
1)从设备里读取输入事件;
2)根据kcm/kl文件,进行按键映射处理;
3)将RawEvent传递给dispatcher处理;
这块代码比较固定,也很少去适配,了解下即可
 
android\frameworks\native\services\inputflinger\*
 
1、InputReader对象启动线程
android\frameworks\native\services\inputflinger\reader\InputReader.cpp
status_t InputReader::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    mThread = std::make_unique<InputThread>(
            "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
    return OK;
}
 
2、InputThread对,提供一个线程模板
android\frameworks\native\services\inputflinger\InputThread.cpp
InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake)
      : mName(name), mThreadWake(wake) {
    mThread = sp<InputThreadImpl>::make(loop);
    mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}
 
3、读取输入事件
//主要使用inotify监测/dev/input,使用epoll监测有无数据
void InputReader::loopOnce() { //作为函数参数传给InputThread
std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis); //读取event设备节点
...
mQueuedListener.flush(); //flush队列,通知dispatcher线程
...
}
2、InputReader和EventHub的分工
1)InputReader(上层角色)

1)如果设备刚接入,则先addDeviceLocked() 添加设备

2)如果设备已存在且有输入事件产生,则processEventLocked()

3)处理事件对象,这里以KeyboardInputMapper为例

2)EventHub(底层角色)

EventHub读取设备节点,获取输入事件RawEvent

1)读取输入设备驱动信息(底层原理是使用inotify/epoll机制进行监测),加载idc/kcm/kl文件(驱动设备提供路径);

2)最终转换成RawEvent,交给InputReader继续处理;

EventHub是linux device交互的功能类

1、openDeviceLocked
android\frameworks\native\services\inputflinger\reader\EventHub.cpp
void EventHub::openDeviceLocked(const std::string& devicePath) {
    ...
    //打开设备
    int fd = open(devicePath.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
    if (fd < 0) {
        ALOGE("could not open %s, %s\n", devicePath.c_str(), strerror(errno));
        return;
    }
 
    ...
    //ioctl得到信号
    // Get device name.
    if (ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
        ALOGE("Could not get device name for %s: %s", devicePath.c_str(), strerror(errno));
    } else {
        buffer[sizeof(buffer) - 1] = '\0';
        identifier.name = buffer;
    }
 
    ...
    //加载idc//kl/kcm
    if (device->classes.any(InputDeviceClass::KEYBOARD | InputDeviceClass::JOYSTICK |
                            InputDeviceClass::SENSOR)) {
        // Load the keymap for the device.
        keyMapStatus = device->loadKeyMapLocked();
    }
}
3)InputReader和EventHub的分工介绍(分层思想)
3、复杂的底层数据结构介绍

1)RawEvent/input_event/Device/InputDevice

1、android\frameworks\native\services\inputflinger\reader\include\EventHub.h
struct RawEvent { //
    // Time when the event happened
    nsecs_t when;
    // Time when the event was read by EventHub. Only populated for input events.
    // For other events (device added/removed/etc), this value is undefined and should not be read.
    nsecs_t readTime;
    int32_t deviceId;
    int32_t type;
    int32_t code;
    int32_t value;
};
 
2、include/linux/input.h
struct input_event{
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};
 
3、Device //注意不是linux device,不要混淆
android\frameworks\native\services\inputflinger\reader\include\EventHub.h
struct Device {
    int fd; // may be -1 if device is closed
    const int32_t id;
    const std::string path;
    const InputDeviceIdentifier identifier; //输入设备的基本信息
    ...
    std::string configurationFile; //对应idc配置文件
   std::unique_ptr<PropertyMap> configuration;
    std::unique_ptr<VirtualKeyMap> virtualKeyMap;
    KeyMap keyMap; //对应两种配置文件,kcm->kl,先找kcm映射,找不到再找kl映射
}
关于Device信息,Android上可以敲dumpsys input查看所有的input设备信息
 
定义
F:\1.code\MTK9256_AN14\android\frameworks\native\services\inputflinger\reader\include\EventHub.h
std::unordered_map<int32_t, std::unique_ptr<Device>> mDevices;
 
 
4、
F:\1.code\MTK9256_AN14\android\frameworks\native\services\inputflinger\reader\include\InputDevice.h
 
class InputDevice { //绑定EventHub的Device,目的是封装Device的复杂操作,给上层提供更方便的操作
private:
    InputReaderContext* mContext;
    int32_t mId;
    int32_t mGeneration;
    int32_t mControllerNumber;
    InputDeviceIdentifier mIdentifier;
    std::unordered_map<int32_t, DevicePair> mDevices;
}
//定义
F:\1.code\MTK9256_AN14\android\frameworks\native\services\inputflinger\reader\include\InputReader.h
std::unordered_map<int32_t /*eventHubId*/, std::shared_ptr<InputDevice>> mDevices GUARDED_BY(mLock);
 
5、
/* Types of input device configuration files. */
enum class InputDeviceConfigurationFileType : int32_t {
    CONFIGURATION = 0,     /* .idc file */
    KEY_LAYOUT = 1,        /* .kl file */
    KEY_CHARACTER_MAP = 2, /* .kcm file */
};

2)KeyMap-KeyLayoutMap-KeyCharacterMap

1、
F:\1.code\MTK9256_AN14\android\frameworks\native\include\input\Keyboard.h
class KeyMap {
public:
    std::string keyLayoutFile;
    std::shared_ptr<KeyLayoutMap> keyLayoutMap;
 
    std::string keyCharacterMapFile;
    std::shared_ptr<KeyCharacterMap> keyCharacterMap;
}
 
2、
class KeyLayoutMap {
private:
    struct Key {
        int32_t keyCode;
        uint32_t flags;
    };
}
kl文件格式举例
key 305   BUTTON_B   // key <linux key> <Android key>
 
3、
class KeyCharacterMap {
private:
    struct Behavior {
        /* The meta key modifiers for this behavior. */
        int32_t metaState = 0;
 
        /* The character to insert. */
        char16_t character = 0;
 
        /* The fallback keycode if the key is not handled. */
        int32_t fallbackKeyCode = 0;
 
        /* The replacement keycode if the key has to be replaced outright. */
        int32_t replacementKeyCode = 0;
    };
 
    struct Key {
        /* The single character label printed on the key, or 0 if none. */
        char16_t label = 0;
 
        /* The number or symbol character generated by the key, or 0 if none. */
        char16_t number = 0;
 
        /* The list of key behaviors sorted from most specific to least specific
         * meta key binding. */
        std::list<Behavior> behaviors;
    };
 
    class Parser {} //加载kcm文件时用此class来解析
}
 
kcm文件格式举例:
1、
key B {
    label:                              'B'  # 印在按键上的文字                       
    base:                               'b'  # 如果没有其他按键(shift, ctrl等)同时按下,此按键对应的字符是'b'                     
    shift, capslock:                    'B'  # 组合按键,如shift+base='B', capslock+base='B'
}
2、
key SPACE {
    label:                              ' '
    base:                               ' '
    alt, meta:                          fallback SEARCH   #组合按键
    ctrl:                               fallback LANGUAGE_SWITCH   #组件按键
}
 
解析组合按键构造三个Behavior
Behavior.metaState=alt
Behavior.fallbackKeyCode=AKEYCODE_SEARCH
 
Behavior.metaState=meta
Behavior.fallbackKeyCode=AKEYCODE_SEARCH
 
Behavior.metaState=ctrl
Behavior.fallbackKeyCode=AKEYCODE_LANGUAGE_SWITCH
 
放到链表std::list<Behavior> behaviors

3)涉及的数据结构总览

2)Dispatcher(CPP)

1、Dispatcher的工作

要点解析

1)输入事件之按键分类:
Global key(一键启动 - 快捷键)
system key(静音、音量、home键)
user key(普通输入按键 )
 
2)inboundQueue/outboundQueue(双端队列 - 用于储存输入事件);
出队:mInboundQueue.pop_front();
入队:mInboundQueue.push_front();
 
3)mCommandQueue(用于储存Command):  
出队:mCommandQueue.pop_front();
入队:mCommandQueue.push_back(command);
2、代码时序图

输入事件有多种类型(触摸、拖动、鼠标等等),这里以输入按键为例(从底层->上层)

3、关键点分析
1)reader与dispatcher的交互实现

1、总体框图

InputFlinger进程

android\frameworks\native\services\inputflinger\host\main.cpp

2、InputManager的构造函数中建立联系,通过mQueuedListener,Reader获得Dispatcher的指针;

android\frameworks\native\services\inputflinger\InputManager.cpp
/**
 * The event flow is via the "InputListener" interface, as follows:
 * InputReader -> UnwantedInteractionBlocker -> InputProcessor -> InputDispatcher
 */
InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
                           InputDispatcherPolicyInterface& dispatcherPolicy) {
    mDispatcher = createInputDispatcher(dispatcherPolicy);
    mProcessor = std::make_unique<InputProcessor>(*mDispatcher);
    mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mProcessor);
    mReader = createInputReader(readerPolicy, *mBlocker);
}

3、处理流 InputReader -> UnwantedInteractionBlocker -> InputProcessor -> InputDispatcher

类图

4、InputListenerInterface

android\frameworks\native\services\inputflinger\InputListener.cpp
 
1、根据NotifyArgs的类型执行对应的函数-泛型思想
void InputListenerInterface::notify(const NotifyArgs& generalArgs) {
    Visitor v{
            [&](const NotifyInputDevicesChangedArgs& args) { notifyInputDevicesChanged(args); },
            [&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(args); },
            [&](const NotifyKeyArgs& args) { notifyKey(args); },
            [&](const NotifyMotionArgs& args) { notifyMotion(args); },
            [&](const NotifySwitchArgs& args) { notifySwitch(args); },
            [&](const NotifySensorArgs& args) { notifySensor(args); },
            [&](const NotifyVibratorStateArgs& args) { notifyVibratorState(args); },
            [&](const NotifyDeviceResetArgs& args) { notifyDeviceReset(args); },
            [&](const NotifyPointerCaptureChangedArgs& args) { notifyPointerCaptureChanged(args); },
    };
    std::visit(v, generalArgs);
}
 
void QueuedInputListener::notifyKey(const NotifyKeyArgs& args) {
    traceEvent(__func__, args.id);
    mArgsQueue.emplace_back(args);
}
 
2、flush - 逐个将mArgsQueue中的事件通过队列传递给dispatcher处理 - 泛型的简洁
void QueuedInputListener::flush() {
    for (const NotifyArgs& args : mArgsQueue) {
        mInnerListener.notify(args);
    }
    mArgsQueue.clear();
}
2)高优先级的按键处理介绍

在PhoneWindowManager.java类中会两个地方会对输入按键进行干预处理

为什么这样设计?因为需要优先处理高优先级的按键(如global/system key)

1、mPolicy.interceptKeyBeforeQueueing();

1)分类-global / system /user key

2)处理紧急事件-比如来电显示

2、mPolicy.interceptKeyBeforeDispatching()

1)响应处理global/system key

2)user key则正常传递到app处理

3、在frameworks中进行按键拦截,也主要在PhoneWindowManager下面两个函数加逻辑

interceptKeyBeforeDispatching()

interceptKeyBeforeQueueing()

3)不同事件按键的处理介绍
不同事件按键会走不同的代码分支,这里介绍下重点
场景分析1-global key(如AKEYCODE_TV - 快捷键)
场景分析2-system key(如AKEYCODE_VOLUME_DOWM - 音量按键)
场景分析3-user key(如AKEYCODE_A、上下左右 - 普通按键)
 
一、第一次预处理-interceptKeyBeforeQueueing
interceptKeyBeforeQueueing的处理结果会储存到事件结构体entry中,后续在InputDispatcher.cpp处理会用到
android/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
interceptKeyBeforeQueueing
 
entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN  //对于没被处理的输入按键事件都是UNKNOWN & POLICY_FLAG_PASS_TO_USER
entry->policyFlags & POLICY_FLAG_PASS_TO_USER
 
 
二、第二次预处理
android/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
interceptKeyBeforeDispatching
 
1、对于system key直接处理,如
switch(keyCode) {
            case KeyEvent.KEYCODE_HOME:
                return handleHomeShortcuts(displayId, focusedToken, event);
            case KeyEvent.KEYCODE_MENU:
...
}
return key_consumed;
 
2、对于global key会发送广播,如
if (isValidGlobalKey(keyCode)
                && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
            return key_consumed;
        }
return key_consumed;
 
3、对于user key, 在此函数内不处理,直接返回
return key_not_consumed;
 
 
三、第二次预处理后的几种返回值
android/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
 
void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
                                                             KeyEntry& entry) {
    const KeyEvent event = createKeyEvent(entry);
    nsecs_t delay = 0;
    { // release lock
        scoped_unlock unlock(mLock);
        android::base::Timer t;
        delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);
        if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
            ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",
                  std::to_string(t.duration().count()).c_str());
        }
    } // acquire lock
 
    if (delay < 0) {
        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP;  //对应system key/global key
    } else if (delay == 0) {
        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;  //对应user key
    } else {
        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER;
        entry.interceptKeyWakeupTime = now() + delay;
    }
}
 
 
 
四、user key的处理
android/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
对于user key而言,dispatchOnce会执行两次,其中第一个循环用于处理global/system key(因为优先级更高一些),第二循环则处理user key(发布到app),代码设计如此,可自行根据代码琢磨。
 
if (runCommandsLockedInterruptable()) {
            nextWakeupTime = LLONG_MIN;
}
mLooper->pollOnce(timeoutMillis); //下一轮循环将处理user key,最终发布到app
4)涉及的Server和Service介绍
1、SystemServer
2、
3、WindowManagerService
wm = WindowManagerService.main(context, inputManager, !mFirstBoot,
                    new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
            ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
 
4、
public class PhoneWindowManager implements WindowManagerPolicy {
    static final String TAG = "WindowManager";
}
 
5、LocalServices.addService(WindowManagerPolicy.class, mPolicy);

3、Reader和Dispatcher的创建和启动

了解到Reader和Dispatcher的作用,再来从宏观上看看创建和启动过程

1)流程图

注意,dispatcher线程要优先与reader线程启动,那样保证消息能第一次时间被处理

3、SystemServer

开机流程:Android系统源码开机启动过程

/android/frameworks/base/services/java/com/android/server/SystemServer.java 

八、APP与输入系统的联系

1、总体交互框图

1)SystemServer通过(connect →inputchannel → socketpair) 将输入事件传递给app

2、inputchannel

1、场景分析 - inputchannel创建过程分析

1)流程图
2)值得注意的点
1、APP远程调用WM服务
产生远程调用的语句:mWindowSession.addToDisplay()细节分析
 
追溯下mWindowSession
 
1、构造函数中又调用自身的构造函数
mWindowSession在构造函数中赋值
public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
}
 
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session, WindowLayout windowLayout) {
        mContext = context;
        mWindowSession = session;
    ...
}
 
2、获取server端session
android\frameworks\base\core\java\android\view\WindowManagerGlobal.java
 
public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession( //获取session句柄,后续使用session访问server接口
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
 
 
public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(  //binder 中 client调用server接口
                        ServiceManager.getService("window"));
                ...
            }
            return sWindowManagerService;
        }
}
 
3、哪里会new ViewRootImpl实例?
android\frameworks\base\core\java\android\view\WindowManagerGlobal.java
mGlobal.addView()中会new一个ViewRootImpl实例
root = new ViewRootImpl(view.getContext(), display);
 
 
 
 
小结:如何找Framework中的远程调用?Binder细节很多,我们只需要关注以下几点即可找到
 
1、client调用:mWindowSession.addToDisplay()
 
2、根据IWindowManager类推断出存在aidl文件:IWindowSession.aidl -> out目录下找IWindowSession.java
 
3、确定server 函数位置:server端必定会extends :IWindowSession.Stub,并根据函数名来双重确定
java/C++层的inputChannel
java/C++层都对应一个 inputChannel类(包含socketpair fd),用于参数传递
1、java层
android\frameworks\base\core\java\android\view\InputChannel.java
2、C++层
android\frameworks\native\libs\input\InputTransport.cpp
 
3、JNI中的转换
jobject inputChannelObj = android_view_InputChannel_createJavaObject(env, std::move(*inputChannel));
C++层inputchannel类
声明:android\frameworks\native\include\input\InputTransport.h
定义:android\frameworks\native\libs\input\InputTransport.cpp
继承于Parcelable,即用于进程间通信的类
1、几个变量
std::string mName;
android::base::unique_fd mFd; //保存socker文件句柄
sp<IBinder> mToken;
 
2、创建client server 对应的两个inputChannel,及对应两个socker
static status_t openInputChannelPair(const std::string& name,
                                         std::unique_ptr<InputChannel>& outServerChannel,
                                         std::unique_ptr<InputChannel>& outClientChannel);
 
3、status_t sendMessage(const InputMessage* msg); //service向fd写msg(按键信息)
 
4、status_t receiveMessage(InputMessage* msg); //client读fd的msg(按键信息)
sockerpair 传递
android\frameworks\native\libs\input\InputTransport.cpp
1、返回给应用程序的fd需要深拷贝,否则返回后会被释放
base::unique_fd InputChannel::dupFd() const {
    android::base::unique_fd newFd(::dup(getFd()));
    ...
    }
    return newFd;
}

3、APP监听输入事件

APP启动时会,先获取到connection,再设置启动监听输入事件,流程如下

4、Looper将输入事件传递给APP

1)从底层传递到上层,流程图如下

2)重点讲解

1、Response、Request结构体
 
struct Response {
        SequenceNumber seq;
        int events;
        Request request;
    };
 
struct Request {
      int fd;
      int ident;
      int events;
      sp<LooperCallback> callback;
      void* data;
 
      uint32_t getEpollEvents() const;
  };
 
2、onInputEvent()是java层处理输入事件的总入口

九、APP层的处理

1、stage

在onInputEvent()函数处理中,还有一系列的传递,用stage来表示各个阶段的处理,其中有针对系统输入法(InputMethodManager)的处理

1)术语

1、ime :input method(可以理解为系统内置输入法),实现

android/frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java

经过输入法处理(app),输出的应是一个kcm对应的字符。

2、synthetic - 综合处理

2)Android层-ViewrootImpl

当前焦点应用的ViewrootImpl对象会收到该消息,并对消息进行分发处理,最终将其发送到对应的View对象中进行界面响应。

3)注意,对于按键类事件:

1、我们关注ime之后的即可;

2、view处理不了再传递给Activity处理;

3、stage主要关注ViewPreImeInputStage/ViewPostImeInputStage;

2、Framework层的类图

1)跟踪代码需要理清以上类的关系

3、代码时序图

重点说明

1、mView/mFocused 以TextView为例

2、按下一个按键的过程(APP)

完成一次按键,按下再松开

按下事件:onkeypreIme -> onkey(setOnKeyListener) -> onkeydown(view) → onkeydown(activity)

松开事件:onkeypreIme -> onkey(setOnKeyListener) -> onkeyup(view) ->onkeyup (activity)

3、mView一般是一个DecorView (extends View) 表示

Activity.java

ViewManager wm = getWindowManager();

wm.addView(mDecor, getWindow().getAttributes());

4、A1 没处理交给 A2,依次类推A3 A4;

十、项目应用

1、系统定制部分-中控-TVAPI

1)从整体的输入系统可知,可以在PhoneWindowManager 中interceptKeyBeforeQueueing/interceptKeyBeforeDispatching加入截取逻辑;

2)PhoneWindowManager 加入中控钩子截取处理,中控处理不了再调用tvapi接口继续处理

3)中控是为了进一步优化系统性能,避免过多的按键影响性能(从整个输入系统来看,流程是相当之多和复杂的,代价就是耗时);

4)在没有中控之前,直接在PhoneWindowManager远程调用tvapi;

1、
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        ...
        //CVTE Android Patch Begin
        if(com.cvte.os.CtvManagerInternal.callGeneric(com.cvte.os.ICtvDataDefine.WHAT_COMMON_HOTKEYMANAGER, "cvteInterceptKeyBeforeQueueing", false, event)){
            return 0;
        }
        //CVTE Android Patch End
}
 
android\vendor\cvte\cvte-central-control\src\com\cvte\server\manager\CvteHotkeyManager.java
public boolean cvteInterceptKeyBeforeQueueing(Object... obj) {
        ... //中控能处理的先处理,处理不了再转发给tvapi
        if (mHotkeyService != null) {
            if(event.getKeyCode() == CtvPerformanceService.CVTE_KEYEVENT_BASE){
                LLog.i(TAG,"TvApi cvteInterceptKeyBeforeQueueing CvtKeyEvent.KEYCODE_UNKNOWN no need process");
                return true;//skip CvtKeyEvent.KEYCODE_UNKNOWN, no need process
            }
            try {
                long begin = SystemClock.uptimeMillis();
                boolean ret = mHotkeyService.handleKeyEventsBefore(event) || (IS_KERNEL_AT && isKeyPadEvent(event)); //转到tvapi处理
                LLog.i(TAG,
                        "TvApi cvteInterceptKeyBeforeQueueing " + KeyEvent.keyCodeToString(event.getKeyCode()) + " : "
                                + KeyEvent.actionToString(event.getAction()) + ", ret:" + ret + ", cost:"
                                + (SystemClock.uptimeMillis() - begin) + "ms");
                return ret;
            } catch (Exception e) {
                LLog.e(TAG, "Unknown Exception", e);
            }
        } else {
            bindService();
            return isIntercept(event) || (IS_KERNEL_AT && isKeyPadEvent(event));
        }
}
 
 
2、
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, int policyFlags) {
        //CVTE Android Patch Begin
        if (com.cvte.os.CtvManagerInternal.callGeneric(com.cvte.os.ICtvDataDefine.WHAT_COMMON_HOTKEYMANAGER,"cvteInterceptKeyBeforeDispatching", false, event)) {
            return -1;
        }
}
 
android\vendor\cvte\cvte-central-control\src\com\cvte\server\manager\CvteHotkeyManager.java
 
public boolean cvteInterceptKeyBeforeDispatching(Object... obj) {
        KeyEvent event = (KeyEvent) obj[0];
 
        ... //中控能处理的先处理,处理不了再转发给tvapi
 
        if (mHotkeyService != null) {
            try {
                // LLog.d(TAG, "enter cvteInterceptKeyBeforeDispatching onGlobalKeyEvent");
                long begin = SystemClock.uptimeMillis();
                boolean ret = mHotkeyService.handleKeyEvents(event); //转到tvapi处理
                LLog.i(TAG,
                        "TvApi KeyBeforeDispatching " + KeyEvent.keyCodeToString(event.getKeyCode()) + " : "
                                + KeyEvent.actionToString(event.getAction()) + ", ret:" + ret + ", cost:"
                                + (SystemClock.uptimeMillis() - begin) + "ms");
                return ret;
            } catch (Exception e) {
                LLog.e(TAG, "Unknown Exception", e);
            }
        } else {
            bindService();
            return isIntercept(event);
        }
        return false;
    }

2、CVTE - 无焦点应用优化处理

1、android\frameworks\base\core\java\android\view\ViewRootImpl.java
private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;
            //cvte add begin
            if(FocusAdapterUtil.getInstance().reflectProcessKeyEventMethodCvte(mView, event)){
                return FINISH_HANDLED;
            }
            //cvte add end
}
 
2、
android/frameworks/base/core/java/com/cvte/specialapkviewfocus/FocusAdapterUtil.java
android/frameworks/base/core/java/com/cvte/specialapkviewfocus/CvteSpecialApkViewFocusHandlerImpl.java
 
这个类的主要功能是处理特定海外主流APK在某些界面上没有焦点的问题。代码中提到了多个特定的应用程序,如Amazon PrimeVideo、Netflix、Gaana和Twitter等,并为这些应用提供了特定的焦点处理逻辑。
比如处理Netflix的主页和播放界面焦点问题,Amazon PrimeVideo的播放和主页焦点问题,以及Gaana和Twitter的登录界面焦点问题,它通过模拟用户操作和系统事件来解决焦点问题,提升用户体验。

3、增加按键映射

https://tvgit.gz.cvte.cn/q/message:PT230025-77

4、APP应用编写代码

只需简单两步即可实现input事件处理

1、实现onKey处理逻辑
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    public interface OnKeyListener {
            boolean onKey(View v, int keyCode, KeyEvent event);
        }
}
2、setOnKeyListener(new View.OnkeyListener(){....});

十一、调试

1、getevent(linux)
                    linux_key 遥控码
/dev/input/event1: 0004 0004 0088908d
/dev/input/event1: 0001 0071 00000001
 
getevent -l
/dev/input/event1: EV_MSC       MSC_SCAN             00889011
/dev/input/event1: EV_KEY       KEY_MUTE             DOWN
 
IR:底层没有配置,则为KEY_UNKNOW
BT:如果缺少hid,会设为默认值Linux ( KEY_UNKNOWN ),此时需要去配HID
 
2、sendevent (linux)
sendevent <目标设备号> <码值>
sendevent /dev/input/event5 1 42 1
 
3、dumpsys (android)
dumpsys input //获取当前设备使用的kl和kcm
注意:IR和BT吃的kl不一样,连接蓝牙设备后 dumpsys input会变!
 
4、input (linux)
input keyevent "num"  //模拟遥控器、键盘、鼠标各种输入设备操作
相关推荐
太空漫步112 小时前
android社畜模拟器
android
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android
yujunlong391914 小时前
android,flutter 混合开发,通信,传参
android·flutter·混合开发·enginegroup
rkmhr_sef14 小时前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb