BUG描述:
添加触摸屏驱动后, apk对触摸事件没有响应.
Linux 层驱动移植
- 内核根目录 make menuconfig
更改 “Device Drivers” ->“HID Devices” 下的 “/dev/hidraw” “PID device support” 和 “/dev/hiddev” 三个位置置成*
- 复制 “ilitek_auv3X.c”和 “usbhid.h” 到内核下的 “drivers/hid/” 文件夹中
在“drivers/hid/Makefile” 文件中添加
“obj-$(CONFIG_INPUT_ILITEK_TOUCH) +=ilitek_auv3X.o”
在drivers/hid/Kconfig 中添加
“config INPUT_ILITEK_TOUCH”
tristate “ILITEK USB touchscreen driver”
内核根目录 make menuconfig
将 “Device Drivers”->”HID Devices”->”Special HID drivers”->ILITEK USB touch screen driver” 的状态置成 ‘*’
问题:将内核编译并烧录进目标板后报错 “hid probe creating class error” 解决:发现是BUS多次调用"hid_probe"注册同一个USB设备所导致的错误。既然是同一个USB设备就有相同的PID VID,如是在drivers/hid/ilitek_auv3X.chid_probe 添加两个静态变量记录PID VID,如果相同则返回不进行任何处理。然后在编译烧入进目标板,
如何是否查看注册成功
# ls /dev/ilitek_ctrl_usb
# cat /proc/bus/input/devices
I: Bus=0003 Vendor=222a Product=0001 Version=0110
N: Name="ILITEK Multi-Touch-V3000"
P: Phys=usb-fsl-ehci.1-1/input0
S: Sysfs=/devices/platform/fsl-ehci.1/usb2/2-1/2-1:1.0/input/input2
U: Uniq=
H: Handlers=event2
B: EV=b
B: KEY=4000000000000
B: ABS=26500000
# ls /dev/input/event
event0 event1 event2
android 层
修改设备权限
# dd if=uramdisk.img of=ramdisk.img.gz skip=64 bs=1
# gunzip ramdisk.img.gz
# mkdir ramdisk; cd ramdisk
# cpio -i < ../ramdisk.img
# vim init.rc (modify the init.rc)
```
添加设备权限
chmod 0777 /dev/ilitek_ctrl_usb
chmod 0777 /dev/input/event2
```
# find . | cpio --create--format='newc' | gzip > ../ramdisk.img
# mkimage -A arm -O linux -T ramdisk-C none -a 0x70308000 -n "Android Root Filesystem" -d ./ramdisk.img./uramdisk.img
在aosp工程目录下将 ilitek_hid.idc 复制到 out/target/product/xxx/system/usr/idc/(注意此文件名应与触摸屏驱动名一致)
烧录镜像,查看反应
发现问题:
在android 进入luncher后,点击触摸屏,并没有任何反应。在终端输入 getevent
getevent
add device 1: /dev/input/event1
name: "mxc_power_key"
add device 2: /dev/input/event0
name: "mxckpd"
add device 3: /dev/input/event2
name: "ILITEKMulti-Touch-V3000"
能发现"ILITEK Multi-Touch-V3000"设备,点击触摸屏能获取到事件
/dev/input/event2: 0003003900000000
/dev/input/event2: 0003003000000001
/dev/input/event2: 000300350000292c
/dev/input/event2: 00030036000011a6
/dev/input/event2: 0000000200000000
/dev/input/event2: 0000000000000000
/dev/input/event2: 0003003900000000
/dev/input/event2: 0003003000000000
/dev/input/event2: 0000000200000000
/dev/input/event2: 0000000000000000
对比了官方正常驱动事件上报流程,发现一致,说明驱动并没有说明问题。
如是使用了一个触摸屏测试程序
@Override
public boolean onTouchEvent(MotionEvent event)
应用层加上log没有任何反应,在其他android机下能实现点击,拖拽,缩放等功能。
-----------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------
Android Framework
既然驱动层没问题应用层没问题,就只能在framework层找问题了在调试过程中需要添加log ,定位问题.
LOG级别
修改/system/core/rootdir/init.rc,把loglevel从3改为7
Android系统启动的log分为Linux内核的log和Android Logger系统的log,
抓取的方法如下:
$ adb shell dmesg > dmesg.txt
$ adb logcat -d -v time -b"main" > main.txt
$ adb logcat -d -v time -b"system" > system.txt
$ adb logcat -d -v time -b"events" > events.txt
由于log数量较多, 需要静态分析log 请使用SecureCRT之自动记录日志功能:
http://jingyan.baidu.com/article/335530da88aa0b19cb41c3b9.html
理解 android输入子系统
android 子系统主要由以下几个模块组成:
EventHub:事件的传入是从EventHub开始的,EventHub是事件的抽象结构,维护着系统设备的运行情况,并维护一个所有输入设备的文件描述符
Input Reader: 负责从硬件获取输入,转换成事件(Event), 并分发给Input Dispatcher.
Input Dispatcher: 将Input Reader传送过来的Events 分发给合适的窗口,并监控ANR。
Input Manager Service:负责Input Reader 和 Input Dispatchor的创建,并提供Policy 用于Events的预处理。
Window Manager Service:管理Input Manager 与 View(Window) 以及 ActivityManager 之间的通信。
View and Activity:接收按键并处理。
ActivityManager Service:ANR 处理
在在framework 层中信息分析输入子系统各个模块函数功能,并添加相应LOG对其验证,发现EventHub InputRead模块运行正常,在InputDispatcher中添加打印时,发现InputDispatch 没有走到最后一步startDispatchCycleLocked函数中的PublishMotionEvent. 如是向上分析发现LOG
"inbound event was dropped because the policy consumed it "
进一步分析发现
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime){
```
```
bool done = false;
DropReason dropReason = DROP_REASON_NOT_DROPPED;
if (!(mPendingEvent->policyFlags &POLICY_FLAG_PASS_TO_USER)) {
dropReason = DROP_REASON_POLICY;
} else if (!mDispatchEnabled) {
dropReason = DROP_REASON_DISABLED;
}
}
设置为DROP_REASON_POLICY主要有两种情形:
A. 在InputReader notify InputDispatcher之前,Policy会判断不需要传递给应用的事件。
B. 在InputDispatcher dispatch事件前,PhoneWindowManager使用方法interceptKeyBeforeDispatching()提前consume掉一些按键事件,interceptKeyBeforeDispatching()主要对HOME/MENU/SEARCH按键的特殊处理,如果此时能被consume掉,那么在InputDispatcher 中将被丢弃。
由于本系统不使用这些按键,如是修改./system/usr/keylayout/下的.kl文件映射值。屏蔽掉它,排除HOME/MENU/SEARCH按键的可能。
验证:touchScreen 还是没反应
如是用了一狠招,将如下return 注释,使dispatcher正常执行到最后一步
boolInputDispatcher::dispatchMotionLocked(
nsecs_t currentTime,MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime){
····
····
// Cleanup if dropping the event.
if (*dropReason != DROP_REASON_NOT_DROPPED) {
setInjectionResultLocked(entry,*dropReason == DROP_REASON_POLICY
?INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
// return true; 修改方法
}
····
····
}
验证:查看LOG没有其他异常,安装触摸屏测试APK发现点击,缩放,拖拽均没有问题
进一步分析
上述问题解决了但是没有找到具体原因,下面进一步分析. 在程序流程前面找原因:
查看前面的调用发现是由于mPendingEvent->policyFlags没有置POLICY_FLAG_PASS_TO_USER 导致dropReason = DROP_REASON_POLICY
voidInputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,
nsecs_t keyRepeatDelay, nsecs_t*nextWakeupTime) {
nsecs_t currentTime = now();
````
````
// Now we have an event to dispatch.
assert(mPendingEvent != NULL);
bool done = false;
DropReason dropReason = DROP_REASON_NOT_DROPPED;
if (!(mPendingEvent->policyFlags &POLICY_FLAG_PASS_TO_USER)) {
dropReason = DROP_REASON_POLICY;
} else if (!mDispatchEnabled) {
dropReason = DROP_REASON_DISABLED;
}
再往上面找mPendingEvent->policyFlags
再往上找mPendingEvent->policyFlags
voidInputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,
nsecs_t keyRepeatDelay, nsecs_t*nextWakeupTime) {
mInboundQueue.dequeue(entry);
mPendingEvent = entry;
}
再找mInboundQueue
void InputDispatcher::dispatchOnce() {
nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ //acquire lock
AutoMutex _l(mLock);
dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, &nextWakeupTime);
if(runCommandsLockedInterruptible()) {
nextWakeupTime =LONG_LONG_MIN; // force next poll to wake upimmediately
}
} //release lock
``
``
}
再找dispatchOnceInnerLocked
void InputDispatcher::dispatchOnce() {
nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ //acquire lock
AutoMutex _l(mLock);
dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, &nextWakeupTime);
if(runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
} //release lock
``
``
}
再找dispatchOnceInnerLocked
void InputDispatcher::notifyMotion(nsecs_teventTime, int32_t deviceId, int32_t source,
uint32_t policyFlags, int32_taction, int32_t flags, int32_t metaState, int32_tedgeFlags,
uint32_t pointerCount, constint32_t* pointerIds, const PointerCoords* pointerCoords,
float xPrecision, floatyPrecision, nsecs_t downTime) {
``
``
policyFlags |= POLICY_FLAG_TRUSTED; //注意此处赋值
mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags);
//执行后并不会影响 policyFlags的值依然为 POLICY_FLAG_TRUSTED 这就便造成了上述出现的直接返回的问题
bool needWake;
{ //acquire lock
AutoMutex _l(mLock);
// Attempt batching and streaming ofmove events.
if (action ==AMOTION_EVENT_ACTION_MOVE) {
NoBatchingOrStreaming:;
}
// Just enqueue a new motion event.
MotionEntry* newEntry =mAllocator.obtainMotionEntry(eventTime,
deviceId, source, policyFlags, action,flags, metaState, edgeFlags,
xPrecision, yPrecision,downTime,
pointerCount, pointerIds,pointerCoords);
needWake =enqueueInboundEventLocked(newEntry); //在此处进队列
} //release lock
if (needWake) {
mLooper->wake();
}
}
在InputReader notifyMotion 中被调用
void TouchInputMapper::dispatchTouch(nsecs_twhen, uint32_t policyFlags,
TouchData* touch, BitSet32 idBits, uint32_tchangedId, uint32_t pointerCount,
int32_t motionEventAction) {
int32_t pointerIds[MAX_POINTERS];
PointerCoords pointerCoords[MAX_POINTERS];
int32_t motionEventEdgeFlags = 0;
float xPrecision, yPrecision;
```
```
xPrecision = mLocked.orientedXPrecision;
yPrecision =mLocked.orientedYPrecision;
} //release lock
getDispatcher()->notifyMotion(when, getDeviceId(), getSources(),policyFlags,
motionEventAction, 0,getContext()->getGlobalMetaState(), motionEventEdgeFlags,
pointerCount, pointerIds,pointerCoords,
xPrecision, yPrecision, mDownTime);
}
通过上述分析是void InputDispatcher::notifyMotion中的mPolicy->interceptGenericBeforeQueueing(eventTime,/byref/ policyFlags);没有更改了policyflag所导致的,于是添加Log查看没有变化前后数值一致 POLICY_FLAG_TRUSTED = 0x0200000, 注意后面0的个数
- 分析interceptGenericBeforeQueueing的定义mPolicy的interceptGenericBeforeQueueing实现在 frameworks\base\services\jni\com_android_server_InputManager.cpp中
void NativeInputManager::interceptGenericBeforeQueueing(nsecs_twhen, uint32_t& policyFlags) {
#if DEBUG_INPUT_DISPATCHER_POLICY
LOGD("interceptGenericBeforeQueueing- when=%lld, policyFlags=0x%x", when, policyFlags);
#endif
//Policy:
// -Ignore untrusted events and pass them along.
// - Nospecial filtering for injected events required at this time.
// -Filter normal events based on screen state.
// - Fornormal events brighten (but do not wake) the screen if currently dim.
if ((policyFlags & POLICY_FLAG_TRUSTED) &&!(policyFlags & POLICY_FLAG_INJECTED)) {
if (isScreenOn()) {
policyFlags |=POLICY_FLAG_PASS_TO_USER;
if (!isScreenBright()) {
policyFlags |= POLICY_FLAG_BRIGHT_HERE;
}
}
} else {
policyFlags |=POLICY_FLAG_PASS_TO_USER;
}
}
policyFlags 前后都为POLICY_FLAG_TRUSTED 因此可以断定是 isScreenOn() 为false 所导致,
继续分析isScreenOn()
android_server_PowerManagerService_isScreenOn
--> gScreenOn
-->android_server_PowerManagerService_nativeSetPowerState
接着提供函数给JNI层调用
-->nativeSetPowerState
分析nativeSetPowerState 在base\services\java\com\android\server\PowerManagerService.java中
private void updateNativePowerStateLocked() {
nativeSetPowerState(
(mPowerState & SCREEN_ON_BIT) != 0,
(mPowerState &SCREEN_BRIGHT) == SCREEN_BRIGHT);
}
这已经涉及到电源管理 framework.
- 由mPowerState的值决定nativeSetPowerState函数的实参 mPowerState是private的经过详细分析由setPowerState 或者 synchronized或者systemReady() 或者acquireWakeLockLocked , 于是在应用直接请求 wakelock。 发现没有用。
- 后来与同事讨论,发现linux层在电源层管理做了某些处理,还有一部分并没有完善。android的电源管理框架在底层调用中可能会出现异常,这就造成Android framework一直以为屏幕是关闭的, 所以触摸事件一直被intercept无法上传到应用层。获取wakelock也起不到作用。考虑到系统不会进行自动息屏休眠, 于是在\frameworks\base\services\jni\com_android_server_InputManager.cpp 文件中的interceptGenericBeforeQueueing函数中强制 policyFlags |= POLICY_FLAG_PASS_TO_USER。最终Android apk应用可以接收到触摸屏事件。
总结
此次bug的解决,通过一层一层的分析,最终找到问题的根源. 涉及到Linux驱动层,android input framework和powermanagerframework. 以及应用层相关监控事件的调用. 关联到的代码量非常大, 不建议直接阅读代码去找问题,因为分析的信息量太大,一不小心走了点弯路,可能就会浪费时间.建议先多阅读安卓官方文档,以及framework 的UML图.将问题规模缩小,然后详细分析代码.
参考:
http://www.360doc.com/content/14/0329/00/10366845_364576767.shtml
http://www.tuicool.com/articles/be2qI3v
http://www.linuxidc.com/Linux/2011-11/47721p2.htm