android 系统触摸屏BUG解决过程分析

  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,把loglevel3改为7

Android系统启动的log分为Linux内核的logAndroid 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 ViewWindow) 以及 ActivityManager 之间的通信。

View and Activity:接收按键并处理。

ActivityManager ServiceANR 处理

在在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

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值