现在的位置: 首页 > 综合 > 正文

Android 耳机系统综述

2018年07月08日 ⁄ 综合 ⁄ 共 22823字 ⁄ 字号 评论关闭

本来是下载里的一个文档,我把里面的内容给拷出来了。

本文为本人随笔所写,欢迎转载。由于个人的见识和能力有限,不能面面俱到,也可能存在谬误,敬请各位指出,本人的邮箱是hyouyan@126.com ,博客是http://hyouyan.cublog.cn

 

在Android 中其实并没有耳机系统这个称呼,只是我为了方便解释而加的。在整个android系统中,跟耳机相关的部分有:

1.      Linux 驱动:主要完成耳机的插入的检测,hook键的检测,其中hook键包括长按和短按。

2.      在frameworks 中的耳机的观察的文件(HeadsetObserver.java ),这个文件主要是检测耳机是否插入和名字,并把相关的内容通过Intent 广播出去。

3.      跟音频相关,改变音频输出的路径(这边涉及到播放音乐和电话部分)。

4.      跟事件的处理相关,这部分主要体现 hook 的功能,主要是接听电话,挂断电话等。

事件的处理又分为linux 的事件处理和android上的事件处理。

 

 我将分块叙述,由于各种原因,我在这不便把源代码公布,如果你需要的我的帮助,可以发邮件给我,也可以在我blog 上留言,谢谢!

 

Linux 驱动

首先要定义一个switch_dev(structswitch_dev sdev; )并把它初始化,如(sdev.name=  ……);然后注册一个switch device :

  ret= switch_dev_register(&switch_data->sdev);

  if(ret < 0)

 {

 goto err_switch_dev_register;

 }

switch_dev_register这个函数在 switch_class.c 中实现

int switch_dev_register(struct switch_dev*sdev)

{

 intret;

  if(!switch_class) {

  ret= create_switch_class();

  if(ret < 0)

  return ret;

 }

 

 sdev->index = atomic_inc_return(&device_count);

 sdev->dev = device_create(switch_class, NULL,

 MKDEV(0, sdev->index), NULL, sdev->name);

 if(IS_ERR(sdev->dev))

 return PTR_ERR(sdev->dev);

  ret= device_create_file(sdev->dev, &dev_attr_state);

  if(ret < 0)

 goto err_create_file_1;

  ret= device_create_file(sdev->dev, &dev_attr_name);

  if(ret < 0)

 goto err_create_file_2;

 

 dev_set_drvdata(sdev->dev, sdev);

 sdev->state = 0;

 return 0;

 

err_create_file_2:

 device_remove_file(sdev->dev,&dev_attr_state);

err_create_file_1:

 device_destroy(switch_class, MKDEV(0, sdev->index));

 printk(KERN_ERR "switch: Failed to register driver %s\n",sdev->name);

 

 return ret;

}

 

这个函数中主要是以下几个函数

l  create_switch_class()

l  device_create(switch_class, NULL, MKDEV(0, sdev->index), NULL,sdev->name);

l  device_create_file(sdev->dev, &dev_attr_state);

l  device_create_file(sdev->dev, &dev_attr_name);

经过以上函数后将会生成路径和被用户空间访问的节点

 

"/sys/class/switc h/h2w/name";

"/sys/class/switch/h2w/state";

这两个供用户空间访问

 

在这个函数中要注意到

static DEVICE_ATTR(state, S_IRUGO |S_IWUSR, state_show, NULL);

static DEVICE_ATTR(name, S_IRUGO | S_IWUSR,name_show, NULL);

这两项中用于设置节点state和name的属性

 

DEVICE_ATTR 有四个参数,分别为名称、权限位、读函数、写函数

有此可以知道state 和name,虽然有读写权限,但都只有读函数,没有写函数。

其中state 对headsetobserver.java 区分有无 mic和耳机是否插入起作用

static ssize_t state_show(struct device*dev, struct device_attribute *attr,

 char *buf)

{

 struct switch_dev *sdev = (struct switch_dev *)

 dev_get_drvdata(dev);

 

  if(sdev->print_state) {    // 如果用户有定义print_state函数,将调用用户定义的

  intret = sdev->print_state(sdev, buf);

   if (ret >= 0)

  return ret;

 }

 return sprintf(buf, "% d\n", sdev->state);// 把sdev->state 以%d的格式装如 buf 中

}

 

在这个函数得注意:如果你想你的frameworks 能区别出有没有mic,并且你用的是switch_gpio.c这个文件的话,你需要把switch_gpio.c中的sdev->print_state 的定义去掉。

我就在这卡了半天的时间。State原先出来一直是1 ,后来才发现原来是自己定义了sdev->print_state 并只返回 0 和1 ,没有其他值。

 

现重新回到driver,接下来时 input 子系统的内容 input_allocate_device(); 分配内存给新的输入设备 接下去初始化input_dev 这个结构体,给输入设备命名dev->name, 设置input 支持的键值 input_set_capability,如:

 input_set_capability(ipdev, EV_KEY,KEY_MEDIA);

 input_set_capability(ipdev, EV_SW,SW_HEADPHONE_INSERT);

  input_set_capability(ipdev, EV_KEY, KEY_END);

  注册input 设备 input_register_device(ipdev);

   在驱动中还涉及到工作队列等问题,就请各位自己去看一下吧。

接下来是对于中断的处理,这个中断方式我是从HTC 的驱动中学的,有点巧妙,想到了叶

就不算巧妙了,呵呵。

  先申请为高电平中断,我的板子是插入耳机检测脚我高电平,在进入中断后再申请为低

电平中断,这个相对于上升和下降有个好处——当设置为上升或下降沿触发中断时,开机之

前插入耳机,当开机后,将识别不到耳机。而当设置为电平触发可以解决这个问题。

  我的观点是在耳机在插槽内时,检测引脚直接被拉倒插入耳机稳定后的电平,而不会产

生上升和下降沿。中断申请的代码如下:

request_irq(gpio_to_irq(18),gpio_irq_handler,IRQF_TRIGGER_HIGH,pdev->name,switch_data);

中断处理的代码如下:

set_irq_type(gpio_to_irq(18),gpio_get_value(18) ? IRQF_TRIGGER_LOW :

IRQF_TRIGGER_HIGH);

由上可以看到C 语言的问号表达式的好处了吧,呵呵。C 语言博大精深!还有很多精

髓的问题,以后用了,慢慢体会,如果你觉得你的C 非常好了,呵呵,找一个C 语言的笔

试题来做做,哈哈,你真会发现又学到一堆的东西。呵呵。继续我们的驱动。

  接下来是有无mic的判断和设置state 的值了,有 HeadsetObserver.java 这个文件中可以

得出state 的值:

  有mic:state 等于1

  没有mic:state 等于 2

扯点题外,我原先以为在"/sys/class/switch/h2w/state";下的state 只有 0 和1 ,我再问了我

的一些同事,他们也跟我说是bool类型。但我看到headsetobserver.java中又有1 和2,

后面觉得有点可疑。再看源代码之前,真的不想看源代码,看了源代码后,发现源代码

真好。哈哈。通过一步步跟,后面发现时可以大于1 的,呵呵。

这个将要用到switch_get_state(&data->sdev)这个函数,它也是在switch_class.c中实现的。

void switch_set_state(struct switch_dev*sdev, int state)

{

 charname_buf[120];

 charstate_buf[120];

 char*prop_buf;

 char*envp[3];

  intenv_offset = 0;

 intlength;

 

  if(sdev->state != state) {

 sdev->state = state;  //实现你要设置的值

   prop_buf = (char *)get_zeroed_page(GFP_KERNEL);

  if(prop_buf) {

  length = name_show(sdev->dev, NULL, prop_buf); //给HeadsetObserver.java 读取

名字

   if(length > 0) {

   if (prop_buf[length - 1] == '\n')

    prop_buf[length - 1] = 0;

   snprintf(name_buf, sizeof(name_buf),

    "SWITCH_NAME=%s", prop_buf);

   envp[env_offset++] = name_buf;

   }

     length = state_show(sdev->dev, NULL, prop_buf); // 给HeadsetObserver.java 读取

读取状态,这个函数我们在前面分析过了,这个函数比较重要,关系到区分有无mic。

 

   if(length > 0) {

   if (prop_buf[length - 1] == '\n')

    prop_buf[length - 1] = 0;

   snprintf(state_buf, sizeof(state_buf),

    "SWITCH_STATE=%s", prop_buf);

   envp[env_offset++] = state_buf;

   }

  envp[env_offset] = NULL;

  kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp);

  free_page((unsigned long)prop_buf);

  }else {

     printk(KERN_ERR "out of memory in switch_set_state\n");

  kobject_uevent(&sdev->dev->kobj, KOBJ_CHANGE);

  }

 }

}

EXPORT_SYMBOL_GPL(switch_set_state); //供外部所使用。

由于hook 键和检测 mic的有关联,故如果有mic则要申请hook 的中断。

具体mic的检测可以参考我的blog 中转载别人的的一篇文章,链接地址如下

 

http://blog.chinaunix.net/u3/106866/showart_2273977.html

 

接下来是HOOK 键功能的处理了,在google 论坛里有些说实现 hook 键接听和挂断电

话的问题。Hook键只有一个,要实现两个功能就得要用时间来区分了,

  短按:代表接听。

  长按:代表拒接。

这样两种功能就实现了,呵呵。对于长短的检测最好用纳秒,用秒的准确性比较低。存在误

判性比较高,可以利用把时间转换成纳秒来计算,我用如下实现检测时间的长短:

do_gettimeofday(&time); 

 timens=timeval_to_ns(&time); 

 while(gpio_get_value(123)==0){};

 do_gettimeofday(&time);

 (timeval_to_ns(&time)-timens)由这个式子可以得到比较准确的时间。

在利用这个时间,你确定一个判断长短的依据,就可以了如:

if( (timeval_to_ns(&time)-timens)<1000000000l)  

{//短按

if( (timeval_to_ns(&time)-timens) > 50000000)// 为了取出噪音,而设置一定的最低值

{

 input_report_key(switch_data->ipdev,KEY_MEDIA,1);

 input_sync(switch_data->ipdev);

 msleep(100);

 input_report_key(switch_data->ipdev,KEY_MEDIA,0);

 input_sync(switch_data->ipdev);

}

}

else

{//长按

  input_report_key(switch_data->ipdev,KEY_END,1);

  input_sync(switch_data->ipdev);

  msleep(100);

  input_report_key(switch_data->ipdev,KEY_END,0);

  input_sync(switch_data->ipdev);

}

在这传上去的是KEY_MEDIA和KEY_END,然而这两个键值又如何对应上层的接听和挂

断呢?其中KEY_END 在frameworks 层已经映射成挂机键了,然而 KEY_MEDIA 却要你自

己映射成HEADSETHOOK键,在你android的根目录下在

sdk\emulator\keymaps下qwerty.kl 中加入

key 226  HEADSETHOOK       WAKE

 

在这说明一下有些地方说是

./development/emulator/keymaps/qwerty.kl

我的是android2.1 的版本,我在我的版本下没发现 qwerty.kl 。我想这可能是版本的差异吧。

 

到此,linux 驱动层算是大体结束了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Frameworks 层耳机相关

启动服务在systemserver.java中

public class SystemServer

{

 public static void main(String[] args)

 {

……

init1(args);

……

}

       public static final void init2() {

       Log.i(TAG, "Entered the Android system server!");

       Thread th r = new ServerThread();

       thr.setName("android.server.ServerThread");

       thr.start();

    }

 

}

 

init1 将会调用到 android_server_SystemServer_init1.cpp

extern "C" int system_init();

static voidandroid_server_SystemServer_init1(JNIEnv* en v, jobject clazz)

{

   system_init();

}

由上可以得到,将调用到System_init.cpp

extern "C" status _tsystem_init()

{

……

runtime->callStatic("com/android/server/SystemServer", "init2");

……

}

 

由上可以看出,此时将调回到systemserver.java,并且调用

public static final void init2() {

Log.i(TAG, "Entered the Android systemserver!");

       Thread th r = new ServerThread();

       thr.setName("android.server.ServerThread");

       thr.start();

}

新建线程

class ServerThread  extends Thread

{

……

public void run()

{

……

try {

Log.i(TAG, "Headset Observer");

   // Listen for wired headset changes

headset = new HeadsetObserver(context); //new a thread to observer headset status

     } catch (Throwable e) {

        Log.e(TAG, "Failu re starting HeadsetObserver", e);

 }

}

}

开始服务:HeadsetObserver.java

class HeadsetObserver extendsUeventObserver

{

  ……

 public HeadsetObserver(Context context) 

 {

  ……

 startObserving(HEADSET_UEVENT_MATCH);

 

   init();  // set initial status

}

}

 

运行以上程序后会一直监测HEADSET_UEVENT_MATCH路径的事件,

HEADSET_UEVENT_MATCH ="DEVPATH=/devices/virtual/switch/h2w";

如果有事件的变化,则会调用

public void onUEvent(UEventObserver.UEventevent)  

{

if (LOG) Log.v(TAG, "Headset UEVENT:" + event.toString());

 

try{

update(event.get("SWITCH_ NAME"),Integer.parseInt(event.get("SWITCH_STATE")));

    }catch (NumberFormatException e) {

           Log.e(TAG, "Could not parse switch state from event " +event);

    }

}

 

private synchronized final voidupdate(String newName, int newState)  

{

……

 mHandler.sendMessageDelayed(mHandler.obtainMessage(0,mHeadsetState,

                                                    mPrevHeadsetState,

                                                    mHeadsetName),//send message

}

一下一段没有考证:但我猜应该是由于这个原因会调用到sendIntents

private final Handler mHandler = newHandler()

{

@Override

public void handleMessage(Message msg)  

{

sendIntents(msg.arg1, msg.arg2,(String)msg.obj);

mWakeLock.release();

}

};

如果有新的事件,将会调用

rivate synchronized final void sendIntents

再调用到

private final void sendIntent

此处填充Intent 。

private final void sendIntent(int headset,int headsetState, int prevHeadsetState, String

headsetName)

{

……

Intent intent = newIntent(Intent.ACTION_HEADSET_PLUG);

intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);

……

if ((headset & HEADSETS_WITH_MIC) !=0)  

{

   microphone = 1;//  是否有 mic

}

if ((headsetState & headset) != 0)

{

  state = 1;

}

intent.putExtra("state", state);

   intent.putExtra("name", headsetName);

intent.putExtra("microphone",microphone);

……

ActivityManagerNative.broadcastStickyIntent(intent,null);    //broadcast intent

}

 

 

 

 

 

跟音频相关

此时在AudioService.java中将接收Broadcast

private class AudioServiceBroadcastReceiverextends BroadcastReceiver

{

public void onReceive(Context context,Intent intent)

{

  ……

 elseif (action.equals(Intent.ACTION_HEADSET_PLUG))

 {

  ……

 //if you first insert headset, will implement fellow code

AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_H

EADSET,AudioSystem.DEVICE_STATE_AVAILABLE,"");

}

}

}

调用setDeviceConnectionState由在android_media_AudioSystem.cpp中可以得到

static JNINativeMethod gMethods[] = {

 

"setDeviceConnectionState","(IILjava/lang/String;)I",(void*)android_media_AudioSystem_

setDeviceConnectionState},

 

};

所以将调用到android_media_AudioSystem_setDeviceConnectionState

android_media_AudioSystem_setDeviceConnectionState(JNIEnv*env, jobject thiz, jint device,

jint state, jstring device_address)

{

  ……

  Intstatus =check_AudioSystem_Command

(AudioSystem::setDeviceConnectionState(static_cast<AudioSystem::audio_devices>(device),

static_cast <AudioSystem::device_connection_state>(state),c_address));

……

}

由上段程序可以看出,将会调用到AudioSystem.cpp中的setDeviceConnectionState

status_t AudioSystem::setDeviceConnectionState(audio_devicesdevice,

                                                 device_connection_state state,

                                                 const char *device_address)

{

   const sp<IAudioPolicyService>& aps =AudioSystem::get_audio_policy_service();

       if (aps == 0) return PERMISSION_DENIED;

 

   return aps->setDeviceConnectionState(device, state, device_address);

}

get_audio_policy_service();这个函数具体做什么我现在还没弄清楚。

一下这边我没找到具体的联系,我通过打印得知会调用到AudioPolicyManager.cpp 的

setDeviceConnectionState 函数,以下的函数很重要,关系到设置输出路径等

status_tAudioPolicyManager::setDeviceConnectionState(AudioSystem::audio_devices device,

                                         AudioSystem::device_connection_state state,

                                                 const char *device_address)

{

  ……

  //handle output devices

if(AudioSystem::isOutputDevice(device))  

{

switch (state)

{

 caseAudioSystem::DEVICE_STATE_AVAILABLE:

  ……

if (AudioSystem::isBluetoothScoDevice(device))

{

  ……

}

 else if (device == AudioSyste m::DEVICE_OUT_WIRED_HEADSET ||

        device == AudioSystem::DEVICE_OUT_WIRED_HEADPHONE) 

{

if (getDeviceForStrategy(STRATEGY_PHONE) ==device &&

               (mPhoneState ==AudioSystem::MODE_IN_CALL ||

          

mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_PHONE))) 

{

                newDevice = device;

else if((getDeviceForStrategy(STRATEGY_SONIFICATION) & device) &&

                              

mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_SONIFICATION))

{

              newDevice =getDeviceForStrategy(STRATEGY_SONIFICATION);

           } 

else if((getDeviceForStrategy(STRATEGY_MEDIA) == device) &&

                              

mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_MEDIA))

{

               newDevice = device;

else if(getDeviceForStrategy(STRATEGY_DTMF) == device &&

                           

mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_DTMF)) 

{

newDevice = device;

}

       }

}

 

}

 

以上两个个主要函数是:

getDeviceForStrategy ,

mOutputs.valueFor(mHardwareOutput)->isUsedByStrategy(STRATEGY_PHONE))

存在以下疑问:

1  :getDeviceForStrategy 的作用是什么?

 

 

boolAudioPolicyManager::AudioOutputDescriptor::isUsedByStrategy(routing_strategystrategy)

{

  for(int i = 0; i < (int)Audio System::NUM_STREAM_TYPES; i++)

{

  if(AudioPolicyManager::getStrategy((AudioSystem::stream_type)i) == strategy&&

           isUsedByStream((AudioSystem::stream_type)i))

{

           return true;

       }

}

return false;

}

这个函数很重要主要是为以后设置为耳机,蓝牙这类的输出.

以上函数会调用到

boolisUsedByStream(AudioSystem::stream_type stream) { return mRefCount[stream] >0 ? true :

false; }

 

这个函数也很重要. 这个函数用到mRefCount 这个数组,

这个函数在voidAudioPolicyManager::AudioOutputDescriptor::changeRefCount 中改变

然而changeRefCount将会在startOutput 调用。

具体什么时候改变mRefCount这个数组,现不是非常的清楚。

 

上面的走完后将设置输出

setOutputDevice(mHardw areOutput,newDevice);

其中newDevice 决定什么样的输出。

 

 

 

 

跟事件的处理相关

文件流程流程

 

KeyinputQueue.java

Com_android_server_KeyInputQueue.cpp

Eventhub.cpp

SystemService.java

WindowManagerService.java

 

       Fig 1

 

从SystemService.java中启动服务:

public class SystemServer

{

……

native public static void init1(String[]args);

 

public static void main(String[] args)

{

  ……

init1(args);

 }

 public static final void init2()  

{

       Log.i(TAG, "Entered the Android system server!");

       Thread th r = new ServerThread();

       thr.setName("android.server.ServerThread");

       thr.start();

}

}

 

运行init1(args);  在com_android_server_SystemServer.cpp 中有

static JNINativeMethod gMethods[] = {

   /* name, signature, funcPtr */

    {"init1", "([Ljava/lang/String;)V", (void*)android_server_SystemServer_init1 },

};

 

而又有如下:

extern "C" int system_init();

 

static voidandroid_server_SystemServer_init1(JNIEnv* en v, jobject clazz)

{

 system_init();

}

 

system_init()将调用在System_init.cpp  中

extern "C" status _tsystem_init()

{

……

 runtime->callStatic("com/android/server/SystemServer","init2");// 这句后将跳会

SystemService.java 中的 init2 。

 …… 

}

 

也即是如下代码

public static final void init2()  

{

       Log.i(TAG, "Entered the Android system server!");

Thread thr = new ServerThread(); //建立一个 service 的线程

thr.setName("android.server.ServerThread");

   thr.start();

}

 

服务线程:

class ServerThread  extends Thread

{

……

public void run()  

{

  ……

       Log.i(TAG, "Window Manager");

       wm = WindowManagerService.main(context, power,

                factoryTest  != SystemServer.FACTORY_TEST_LOW_LEVEL);

 ServiceManager.addService(Context.WINDOW_SERVICE, wm);

((ActivityManagerService)ServiceManager.getService("activity"))

                    .setWindowManager(wm);

  ……

 }

……

}

 

将跳转到WindowManagerService.java的main 中

public static WindowManagerServicemain(Context context,

PowerManagerService pm, booleanhaveInputMethods)  

{

WMThread thr = new WMThread(context, pm,haveInputMethods);//建立线程

   thr.start();

synchronized (thr)

{

         while (thr.mService == null)

   {

          try {

                thr.wait();

              } catch (InterruptedException e){}

        }

    }

       return thr.mService;

}

static class WMThread extends Thread

{

……

public void run()  

{

……

WindowManagerService s = newWindowManagerService(mContext, mPM,

                    mHaveInputMethods); // 新建一个 WindowManagerService 的线程

  ……

 }

}

即将跳到WindowManagerService的构造函数

private WindowManagerService(Contextcontext, PowerManagerService pm,

           boolean haveInputMethods) 

{

……

mQueue = new KeyQ();

……

}

private class KeyQ extends KeyInputQueue

在KeyInputQueue的构造函数中

KeyInputQueue(Context context,HapticFeedbackCallback   hapticFeedbackCallback)

{

……

mThread.start();

}

Thread mThread = newThread("InputDeviceReader")

{

……

readEvent(ev);

……

}

private static native boolean readEvent(RawInputEventoutEvent);由这句可知readEvent在JNI

在com_android_server_KeyInputQueue.cpp中有

static JNINativeMethod gInputMethods[] = {

   /* name, signature, funcPtr */

    {"readEvent",      "(Landroid/view/RawInputEvent;)Z",

           (void*) android_server_KeyInputQueue_readEvent },

……

}

由以上可知在KeyInputQueue.java中调的 readEvent 将调用到

com_android_server_KeyInputQueue.cpp 的android_server_KeyInputQueue_readEvent。

 

static jboolean  

android_server_KeyInputQueue_readEvent(JNIEnv*env, jobject clazz, jobject event)

{

……

if (hub == NULL)  

{

       hub = new EventHub;

       gHub = hub;

}

……

bool res = hub->getEvent(&deviceId,&type, &scancode, &keycode,

           &flags, &value, &when);

……

}

hub->getEvent 将调用EventHub.cpp 的getEvent 函数

bool EventHub::getEvent(int32_t*outDeviceId, int32_t* outType,

       int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,

       int32_t* outValue, nsecs_t* outWhen)

{

  ……

 if(!mOpened) 

{

       mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR;

       mOpened = true;

    }

 

}

 

openPlatformInput()将扫描/dev/input 下的所有 event并打开它

/*

  *Open the platform-specific input device.

 */

bool EventHub::openPlatformInput(void)

{

  ……

  res= scan_dir(device_path);// 其中static const char *device_path = "/dev/input";

……

}

 

 

 

 

int EventHub::scan_dir(const char *dirname)

{

  char devname[PATH_MAX];

   char *filename;

   DIR *dir;

   struct dirent *de;

   dir = opendir(dirname);

   if(dir == NULL)

       return -1;

   strcpy(devname, dirname);

   filename = devname + strlen(devname);

*filename++ = '/';

// 扫描/dev/input 下的所有event并打开它

   while((de = readdir(dir))) {

       if(de->d_name[0] == '.' &&

          (de->d_name[1] == '\0' ||

           (de->d_name[1] == '.' && de->d_name[2] == '\0')))

           continue;

       strcpy(filename, de->d_name);

       open_device(devname);//打开event设备

  

    }

   closedir(dir);

   return 0;

}

 

int EventHub::open_device(const char*deviceName)

{

……

fd = open(deviceName, O_RDWR);

……

if ((device->classes&CLASS_KEYBOARD)!= 0)  

{

char tmpfn[sizeof(name)];

   char keylayoutFilename[300];

 

   // a more descriptive name

   device->name = name;

 

       // replace all the spaces with underscores

   strcpy(tmpfn, name);

       for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ''))

           *p = '_';

 

       // find the .kl file we need for this device

   const char* root = getenv("ANDROID_ROOT");

   snprintf(keylayoutFilename, sizeof(keylayoutFilename),

                "%s/usr/keylayout/%s.kl", root, tmpfn);

   bool defaultKeymap = false;

   if (access(keylayoutFilename, R_OK))

{

     snprintf(keylayoutFilename, sizeof(keylayoutFilename),

                    "%s/usr/keylayout/%s", root, "qwerty.kl");

       defaultKeymap = true;

   }

  device->layoutMap->load(keylayoutFilename);

}

如果上面的操作都成功则把所有设备都打开了,根据注册的 input 设备的名字查找对应的.kl

文件,如果有该设备就用该.kl把扫描码映射键码。文件现回到EventHub::getEvent。

 

release_wake_lock(WAKE_LOCK_ID);

 

   pollres = poll(mFDs, mFDCount, -1);

 

   acquire_wake_lock(PARTIA L_WAKE_LOCK, WAKE_LOCK_ID);

在这边poll ,如果没有新事件将在这等待,如果有则开始下面的读事件

     res = read(mFDs[i].f d, &iev, sizeof(iev));

 

   到此整个从上面开始的读过程结束。

现在回到

Thread mThread = newThread("InputDeviceReader")

{

……

readEvent(ev);

……

else

{

 send = preprocessEvent(di, ev);

}

}

由这个abstractboolean preprocessEvent(InputDevice device, RawInputEvent event);可以看出上

面调用的preprocessEvent将调到windowmanagerservice.java中的

boolean preprocessEvent(InputDevice device,RawInputEvent event)

 

 

boolean preprocessEvent(InputDevice device,RawInputEvent event)

{

 

if (mPolicy.preprocessInputEventTq(event))

{

     return true;

    }

 

   switch (event.type) 

{

 case RawInputEvent.EV_KEY: 

{

  ……

   if ((actions & WindowManagerPolicy.ACTION_PASS_TO_USER) != 0) // 这段代码不

是很清楚,做什么用的

{                         

if (event.value != 0 &&mPolicy.isAppSwitchKeyTqTiLwLi(event.keycode))  

{

              filterQueue(this);

              mKeyWaiter.appSwitchComing();

           }

           return true;

       } 

else

{

           return false;

       }

往事件队列里放入事件

 

在WindowManagerService.java的构造函数中又有

mInputThread = new InputDispatcherThread();

InputDispatcherThread  线程实际上从   KeyQ  的事件队列中读取按键事件

mInputThread.start();

又有如下

       private final class InputDispatcherThread extends Thread {

                // Time to wait when there isnothing to do: 9999 seconds.

       static final int LONG_WAIT=9999*1000;

       public InputDispatcherThread() {

           super("InputDispatcher");

       }

      @Override

       public void run() {

           while (true) {

                try {

                    process();

                } catch (Exception e) {

                    Log.e(TAG, "Exceptionin input dispatcher", e);

                }

           }

       }

private void process()  

{

……

while (true)

{

 

// Retrieve next event, waiting only aslong as the next

       // repeat timeout.    If theconfiguration has changed, then

       // don't wait at all -- we'll report the change as soon as

       // we have processed all events.

   QueuedEvent ev = mQueue.getEvent(

                    (int)((!configChanged&& curTime < nextKeyTime)

                            ?(nextKeyTime-curTime) : 0));

……

switch (ev.classType)

{

case RawInputEvent.CLASS_KEYBOARD:

if (ke.isDown())

{

   lastKey = ke;

   downTime = curTime;

   keyRepeatCount = 0;

   lastKeyTime = curTime;

   nextKeyTime = lastKeyTime+ ViewConfiguration.getLongPressTimeout();

       if (DEBUG_INPUT) Log.v(TAG, "Received key down: first repeat @"

                                        +nextKeyTime);

else

{

   lastKey = null;

   downTime = 0;

   // Arbitrary long timeout.

   lastKeyTime = curTime;

       nextKeyTime = curTime + LONG_WAIT;

       if (DEBUG_INPUT) Log.v(TAG, "Received key up: ignore repeat @"

                                        +nextKeyTime);

}

dispatchKey((KeyEvent)ev.event, 0, 0);    //发布事件

mQueue.recycleEvent(ev);

break;

……

}

 

 

   /**

         * @return Returns true if event was dispatched, false if it was droppedfor any reason

    */

       private int dispatchKey(KeyEvent event, int pid, int uid) {

       if (DEBUG_INPUT) Log. v(TAG, "Dispatch key: " + event);

 

       Object focusObj = mKeyWaiter.waitForNextEventTarget(event, null,

                null, false, false, pid, uid);

       if (focusObj == null) {

                        Log.w(TAG, "Nofocus window, dropping: " + event);

           return INJECT_FAILED;

       }

       if (focusObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) {

           return INJECT_SUCCEEDED;

       }

 

                // Okay we have finishedwaiting for the last event to be processed.

       // First off, if this is a repeat event, check  to see if there is

       // a corresponding up even t in the queue.    If there is, we will

       // just drop the repeat, because it makes no sense to repeat after

                // the user has released akey.    (This is especially important for

       // long presses.)

       if (event.getRepeatCount() > 0 &&mQueue.hasKeyUpEvent(event)) {

           return INJECT_SUCCEEDED;

       }

        

       WindowState focus = (WindowState)focusObj;

 

       if (DEBUG_INPUT) Log.v(

                        TAG, "Dispatchingto " + focus + ": " + event);

 

                if (uid != 0 && uid !=focus.mSession.mUid) {

           if (mContext.checkPermission(

                    android.Manifest.permission.INJECT_EVENTS,pid, uid)

                    !=PackageManager.PERMISSION_GRANTED) {

                Log.w(TAG, "Permissiondenied: injecting key event from pid "

                        + pid + " uid" + uid + " to window " + focus

                        + " owned by uid" + focus.mSession.mUid);

                return INJECT_NO_PERMISSION;

           }

       }

 

       synchronized(mWindowMap) {

           mKeyWaiter.bindTargetWindowLocked(focus);

       }

 

       // NOSHIP extra state logging

       mKeyWaiter.recordDispatchState(event, focus);

       // END NOSHIP

 

       try {

           if (DEBUG_INPUT || DEBUG_FOCUS) {

                Log.v(TAG, "Delivering key" + event.getKeyCode()

                       + " to "+ focus);

           }

           focus.mClient.dispatchKey(event);

           return INJECT_SUCCEEDED;

       } catch (android.os.RemoteException e) {

                        Log.i(TAG, "WINDOWDIED during key dispatch: " + focus);

           try {

                removeWindow(focus.mSession,focus.mClient);

           } catch (java.util.NoSuchElementException ex) {

                // This will happen if thewindow has already been

                // removed.

            }

       }

 

       return INJECT_FAILED;

}

 

到此先结束,后续有时间,将会把有些问题理清。谢谢你的阅读。共享知识!!

抱歉!评论已关闭.