Android-普通按键和蓝牙耳机按键处理流程详解

前言:

      普通按键目前都会走input event的流程,蓝牙耳机按键目前高通都是走AVRCP里面的逻辑,不走input event流程。

input evnet流程:

手机开机时就会注册一些设备节点专门用来上报某些事件,比如触摸屏幕、开关屏幕、手机音量条等。也会有动态注册的设备节点,如果数字耳机插入就会注册一个节点。每当有对应节点的事件时就会上报事件给input driver,input driver调用到AP侧的input server。input server会把事件Dispatch到PhoneWindowManager做处理。流程如下。

怎么用adb命令查看当前的事件呢:

adb shell getevent

 结果如下:

// 这些是默认就加载好的input
add device 1: /dev/input/event8 // 3.5mm耳机相关
  name:     "kona-mtp-snd-card USB_3_5 Jack"
add device 2: /dev/input/event7 // 模拟耳机按键相关???
  name:     "kona-mtp-snd-card Button Jack"
add device 3: /dev/input/event6 // 和4段式耳机相关
  name:     "kona-mtp-snd-card Headset Jack"
add device 4: /dev/input/event5 // 这个是和屏幕操作(点击、滑动等)有关的
  name:     "fts"
add device 5: /dev/input/event2
  name:     "uinput-goodix"
add device 6: /dev/input/event1 // 这个是和开关屏幕、手机音量有关的
  name:     "qpnp_pon"
add device 7: /dev/input/event4 // 手机音量有关???
  name:     "gpio-keys"
add device 8: /dev/input/event0
  name:     "xiaomi-touch"
add device 9: /dev/input/event3 // 这个是和震动相关的
  name:     "aw8697_haptic"
 
// 插入usb耳机之后会有
add device 10: /dev/input/event9 // 动态添加一个input设备
  name:     "Xiaomi Mi Dual Driver Earphones Type-C"
// 按耳机音量上键
/dev/input/event9: 0004 0004 000c00e9
/dev/input/event9: 0001 0073 00000001
/dev/input/event9: 0000 0000 00000000
/dev/input/event9: 0004 0004 000c00e9
/dev/input/event9: 0001 0073 00000000
/dev/input/event9: 0000 0000 00000000
// 按耳机音量下键
/dev/input/event9: 0004 0004 000c00ea
/dev/input/event9: 0001 0072 00000001
/dev/input/event9: 0000 0000 00000000
/dev/input/event9: 0004 0004 000c00ea
/dev/input/event9: 0001 0072 00000000
/dev/input/event9: 0000 0000 00000000
// 拔出耳机
remove device 10: /dev/input/event9

 事件处理流程:

// 从input的native层调用到java中
// frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) 
// frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags)
.....
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) 
// 此处就会判断怎么处理,首先会判断activity是否要处理
// 如果是ACTION_PASS_TO_USER就会调用mediasession的sendVolumeKeyEvent

// frameworks/base/media/java/android/media/session/MediaSessionLegacyHelper.java
public void sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly)
// frameworks/base/media/java/android/media/session/MediaSessionManager.java
public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) 
// frameworks/base/services/core/java/com/android/server/media/MediaSessionService.java
public void dispatchVolumeKeyEvent(String packageName, String opPackageName, boolean asSystemService, 
        KeyEvent keyEvent, int stream, boolean musicOnly) {
      mVolumeKeyEventHandler.handleVolumeKeyEventLocked(packageName, pid, uid,
          asSystemService, keyEvent, opPackageName, stream, musicOnly);              
}
void handleVolumeKeyEventLocked
void handleKeyEventLocked
private void dispatchVolumeKeyEventLocked(String packageName, String opPackageName, int pid, int uid, 
         boolean asSystemService, KeyEvent keyEvent, int stream, boolean musicOnly) {
    switch (keyEvent.getKeyCode()) {
        case KeyEvent.KEYCODE_VOLUME_UP:
            direction = AudioManager.ADJUST_RAISE; 
            break; 
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            direction = AudioManager.ADJUST_LOWER;
            break;   
    }
    int flags = AudioManager.FLAG_FROM_KEY; 
    // 这里会判断是音量加减还是会有其他,例如按音量上就开始播放音乐等逻辑
    dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid, asSystemService, stream, direction, flags);        
}

private void dispatchAdjustVolumeLocked
session.adjustVolume // 最终会调用audioservice的adjustStreamVolumeForUid调整音量

蓝牙媒体事件处理流程:

// vendor/qcom/opensource/commonsys/packages/apps/Bluetooth/src/com/android/bluetooth/avrcp/Avrcp.java
void handlePassthroughCmd(int op, int state) {
    mMediaSessionManager.dispatchMediaKeyEvent(event);
}

// frameworks/base/media/java/android/media/session/MediaSessionManager.java
dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock)
dispatchMediaKeyEventInternal(@NonNull KeyEvent keyEvent, boolean needWakeLock)

// frameworks/base/services/core/java/com/android/server/media/MediaSessionService.java
public void dispatchMediaKeyEvent(String packageName, boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
    final long token = Binder.clearCallingIdentity();
    mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
            new MediaKeyListenerResultReceiver(packageName, pid, uid,
            asSystemService, keyEvent, needWakeLock));
    mMediaKeyEventHandler.handleMediaKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, needWakeLock);
}
handleMediaKeyEventLocked
handleKeyEventLocked
void handleKeyEventLocked(String packageName, int pid, int uid,
                    boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock,
                    String opPackageName, int stream, boolean musicOnly) {
    // 此处会去判断当前的key是什么内容,比如是音量下,还会判断是否是第一次按下按钮
    dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,keyEvent, needWakeLock);
}

private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,  boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
    MediaSession.Token token = mCustomMediaKeyDispatcher.getMediaSession(keyEvent, uid, asSystemService);
    MediaSessionRecord session = getMediaSessionRecordLocked(token);
    session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
             needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver); // 这个函数很重要,具体处理最终是在onMediaButtonEvent中
    for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr : mCurrentFullUserRecord.mOnMediaKeyEventDispatchedListeners.values()) {
        cr.callback.onMediaKeyEventDispatched(keyEvent, session.getPackageName(), session.getSessionToken());
    }
}

 看下onMediaButtonEvent函数的具体实现:

// frameworks/base/media/java/android/media/session/MediaSession.java
public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
    switch (ke.getKeyCode()) {
        case KeyEvent.KEYCODE_MEDIA_PLAY:
            onPlay();
            return true;
         case KeyEvent.KEYCODE_MEDIA_PAUSE:
            onPause();
            return true;   
         case KeyEvent.KEYCODE_MEDIA_NEXT:   
            onSkipToNext();
            return true; 
         .....
    }
}

蓝牙音量按键处理流程:

// 蓝牙模块调用volume_change_cb,实际调用的就是btavrcp_volume_change_callback

// vendor/qcom/opensource/commonsys/packages/apps/Bluetooth/jni/com_android_bluetooth_avrcp.cpp
btavrcp_volume_change_callback 这里会调用volumeChangeRequestFromNative

// vendor/qcom/opensource/commonsys/packages/apps/Bluetooth/src/com/android/bluetooth/avrcp/Avrcp_ext.java 
volumeChangeRequestFromNative 发送消息 MSG_NATIVE_REQ_VOLUME_CHANGE
// 收到消息就会打印: Avrcp_ext: MSG_NATIVE_REQ_VOLUME_CHANGE: volume=8 ctype=13 local vol 20 remote vol 17
// 处理消息时分两种情况,绝对音量和相对音量。绝对音量控制默认处于开启状态。绝对音量就是音量的百分比,手机和耳机可以保持一致。
// 手机会将音量信息和未衰减的音频发送到耳机。然后,耳机会根据音量信息放大音频,以便用户听到准确的播放音量。
// 手机还可以注册接收音量通知。进行此项注册后,当用户使用接收器上的控件更改音量时,接收器便会向音频源发送通知。这样一来,音频源便能够准确地在界面上显示音量信息。
处理消息时会调用notifyVolumeChanged
private void notifyVolumeChanged(int volume, boolean isShowUI) 调用mAudioManager.setStreamVolume
// frameworks/base/media/java/android/media/AudioManager.java
public void setStreamVolume(int streamType, int index, int flags)

总结:

input event处理流程:

input driver  --->   input server   ---> PhoneWindowManager --->   Activity or MediaSession

蓝牙媒体事件处理流程:

蓝牙driver   ---> 蓝牙APP(AVRCP)  ---> MediaSession

蓝牙音量事件处理流程:

蓝牙driver   ---> 蓝牙APP(AVRCP)  ---> AudioManager

 

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中,View的按键派发流程可以分为三个阶段:事件捕获阶段、事件处理阶段和事件分发阶段。具体流程如下: 1. 事件捕获阶段:从根View开始,依次向下遍历其所有的子View,直到找到最深层的子View。在这个过程中,每个View都有机会处理该事件,即调用onKeyDown()、onKeyUp()等方法进行事件处理。 2. 事件处理阶段:当找到最深层的子View之后,事件开始进行处理。在这个阶段,View会根据自身的状态和属性来处理该事件,例如,判断是否处于可用状态、是否需要获取焦点等。 3. 事件分发阶段:当View处理完该事件之后,事件会根据事件分发规则,向上传递给父View进行处理。如果父View需要处理该事件,则继续进行事件捕获和事件处理阶段;如果不需要处理,则事件传递到下一个父View进行处理,直到传递到根View,或者事件被某个View消费掉。 需要注意的是,在事件分发阶段,View可以通过返回值来控制事件是否被消费。如果View处理了该事件,并认为该事件不需要再传递给下一个View,可以返回true,表示该事件已被消费;如果View没有处理该事件,或者认为该事件需要继续传递给下一个View,可以返回false,表示该事件需要继续传递。 总之,View的按键派发流程是一个非常复杂的过程,需要开发者深入理解和掌握。只有理解了该流程,才能正确地处理按键事件,提升应用程序的交互性和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值