Android TV按键事件Framework层分发处理流程解析

前言

最近项目中遇到一个问题,在客户的launcher上按下 menu 键之后按上下左右键都没有响应,虽然最后通过修改文件 PhoneWindow.java 解决了问题,但是感觉对TV按键事件的分发流程还不是很明白,这里就带着问题来跟踪源码去探索TV 按键事件在 Framework 到底是怎么传递下来的。本篇博客分析的源码是Amlogic平台 Android 5.1 的代码,其它高版本也是适用的。

本篇博客分为以下几个部分:

1、此次项目中menu按键问题的分析定位
2、TV按键事件的分发流程

第一部分主要是这个问题的分析,对此部分不感兴趣的同学可以看直接第二部分。

一、Menu按键问题分析

这个问题是在客户的launcher上按下 menu 键之后按上下左右键都没有响应,只有再按一下 Back 键或者 Menu 键才能恢复正常。我截取了部分log如下

D/WindowManager( 4169): interceptKeyTq keycode=82 interactive=true keyguardActive=false policyFlags=22000000
D/WindowManager( 4169): interceptKeyTi keyCode=82 down=true repeatCount=0 keyguardOn=false mHomePressed=false canceled=false
V/PinyinIME( 4709): onKeyDown:82
I/PinyinIME( 4709): onKeyDown:System handler the keyevent
D/WindowManager( 4169): interceptKeyTq keycode=82 interactive=true keyguardActive=false policyFlags=22000000
D/WindowManager( 4169): interceptKeyTi keyCode=82 down=false repeatCount=0 keyguardOn=false mHomePressed=false canceled=false
I/PinyinIME( 4709): onKeyUp:82
I/PinyinIME( 4709): onKeyUp:System handler the keyevent
--------- beginning of system
V/WindowManager( 4169): Adding window Window{33b8842d u0 AtchDlg:com.unitedview.phenix.launcher/com.unitedview.phenix.launcher.activity.LauncherActivity} at 2 of 4 (after Window{371c5e78 u0 com.unitedview.phenix.launcher/com.unitedview.phenix.launcher.activity.LauncherActivity})
D/WindowManager( 4169): interceptKeyTq keycode=22 interactive=true keyguardActive=false policyFlags=22000000
D/WindowManager( 4169): interceptKeyTi keyCode=22 down=true repeatCount=0 keyguardOn=false mHomePressed=false canceled=false
D/WindowManager( 4169): Unhandled key: win=Window{33b8842d u0 AtchDlg:com.unitedview.phenix.launcher/com.unitedview.phenix.launcher.activity.LauncherActivity}, action=0, flags=8, keyCode=22, scanCode=106, metaState=0, repeatCount=0, policyFlags=1644167168
D/WindowManager( 4169): No fallback.
D/WindowManager( 4169): interceptKeyTq keycode=22 interactive=true keyguardActive=false policyFlags=22000000
D/WindowManager( 4169): interceptKeyTi keyCode=22 down=false repeatCount=0 keyguardOn=false mHomePressed=false canceled=false
D/WindowManager( 4169): Unhandled key: win=Window{33b8842d u0 AtchDlg:com.unitedview.phenix.launcher/com.unitedview.phenix.launcher.activity.LauncherActivity}, action=1, flags=8, keyCode=22, scanCode=106, metaState=0, repeatCount=0, policyFlags=1644167168
D/WindowManager( 4169): No fallback.
D/WindowManager( 4169): interceptKeyTq keycode=21 interactive=true keyguardActive=false policyFlags=22000000
D/WindowManager( 4169): interceptKeyTi keyCode=21 down=true repeatCount=0 keyguardOn=false mHomePressed=false canceled=false
D/WindowManager( 4169): Unhandled key: win=Window{33b8842d u0 AtchDlg:com.unitedview.phenix.launcher/com.unitedview.phenix.launcher.activity.LauncherActivity}, action=0, flags=8, keyCode=21, scanCode=105, metaState=0, repeatCount=0, policyFlags=1644167168
D/WindowManager( 4169): No fallback.


通过这个log看,貌似是按下 Menu 键后,添加了一个新的window,但是在这个window上没有响应按键事件,导致按上下左右键没有作用。我怀疑是这个第三方的launcher添加了一个类似于menu菜单的东西,因为我在其他应用的界面按 Menu 键不会出现这种情况。为了验证我的猜想。我自己写了一个app,按键弹出menu菜单。代码如下:

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    public boolean onOptionsItemSelected(MenuItem item) {
        //创建菜单项的点击事件
        switch (item.getItemId()) {
            case R.id.mune_enter:
                Toast.makeText(this, "点击了登陆", Toast.LENGTH_SHORT).show();
                break;
            case R.id.mune_setting:
                Toast.makeText(this, "点击了设置", Toast.LENGTH_SHORT).show();

                break;
            case R.id.mune_out:
                Toast.makeText(this, "点击了退出", Toast.LENGTH_SHORT).show();
                break;

            default:
                break;
        }

        return super.onOptionsItemSelected(item);
    }
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.d("song","keyCode="+keyCode+",action="+event.getAction());
        if (keyCode==82 && event.getAction()==KeyEvent.ACTION_DOWN){
            Log.d("song","menu键被按了");
        }
        return super.onKeyDown(keyCode, event);
    }
//onCreateOptionsMenu 方法会响应menu 按键弹出menu菜单,此时焦点是在这个menu 的window上的。
在menu菜单弹出的时候也会出现add window 的log

当我满心欢喜的告诉客户说这个是他们的问题的时候结果他们说他的代码里面没有处理menu键,而且这个问题之前在另个一项目中我的B同事解决过,然后我找了下老B同事的提交记录,发现他是这么改的:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java 中注释掉这个menu键Down 和UP的处理逻辑。

protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {
    ····
    case KeyEvent.KEYCODE_MENU: {
                //onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
                 //       event);
                return true;
            }
    ····
}
 ····
 ····
 protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {
   ····
   case KeyEvent.KEYCODE_MENU: {
        //onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
          //       event);
          return true;
       }
   ····
 }

我试了下还真是可以的,这下我就不淡定了,为什么是改这里就可以了,难不成还真是我们系统的问题?这个问题我必须要刨根问题把它整明白!于是就有了第二部分的TV按键事件的分发流程。

二、TV按键事件的分发流程

D/song    ( 4147): InputManagerService---interceptKeyBeforeDispatching---KeyCode=82
D/song    ( 4147): PhoneWindowManager---interceptKeyBeforeDispatching---KeyCode=82
D/WindowManager( 4147): interceptKeyTi keyCode=82 down=true repeatCount=0 keyguardOn=false mHomePressed=false canceled=false
V/PinyinIME( 4542): onKeyDown:82
I/PinyinIME( 4542): onKeyDown:System handler the keyevent
D/song    ( 4564): PhoneWindow---DecorView.dispatchKeyEvent---KeyCode=82
D/song    ( 4564): View--dispatchKeyEvent--KeyCode=82
D/song    ( 4564): handled=false,cb=com.unitedview.phenix.launcher.activity.LauncherActivity@3782a18b,mFeatureId=-1
D/song    ( 4564): ViewRootImpl---mView.dispatchKeyEvent(event)---KeyCode=82
D/WindowManager( 4147): interceptKeyTq keycode=82 interactive=true keyguardActive=false policyFlags=22000000
D/song    ( 4147): InputManagerService---interceptKeyBeforeDispatching---KeyCode=82
D/song    ( 4147): PhoneWindowManager---interceptKeyBeforeDispatching---KeyCode=82
D/WindowManager( 4147): interceptKeyTi keyCode=82 down=false repeatCount=0 keyguardOn=false mHomePressed=false canceled=false
I/PinyinIME( 4542): onKeyUp:82
I/PinyinIME( 4542): onKeyUp:System handler the keyevent
[  232.873096@0] aml_audio_hw: audio_set_aiubuf channel == 8
[  232.878284@0] aml_spdif_dai: aml_hw_iec958_init,runtime->rate=48000, same source mode(1)
[  232.886505@0] aml_spdif_dai: share the same clock
[  232.890997@0] aml_spdif_dai: iec958 mode PCM32
[  232.895404@0] aml_audio_hw: IEC958 PCM32
[  232.899299@0] aml_snd_card_g9tv aml_g9tv_snd.51: i2s/958 same source
[  232.905754@0] aml_snd_card_g9tv aml_g9tv_snd.51: 8ch PCM output->notify HDMI
[  232.915052@3] aml_snd_card_g9tv aml_g9tv_snd.51: I2S playback enable
[  232.918788@3] aml_snd_card_g9tv aml_g9tv_snd.51: IEC958 playback enable
D/song    ( 4564): PhoneWindow---DecorView.dispatchKeyEvent---KeyCode=82
D/song    ( 4564): View--dispatchKeyEvent--KeyCode=82
D/song    ( 4564): handled=false,cb=com.unitedview.phenix.launcher.activity.LauncherActivity@3782a18b,mFeatureId=-1
--------- beginning of system
V/WindowManager( 4147): Adding window Window{25bd8161 u0 AtchDlg:com.unitedview.phenix.launcher/com.unitedview.phenix.launcher.activity.LauncherActivity} at 2 of 4 (after Window{2a5d7431 u0 com.unitedview.phenix.launcher/com.unitedview.phenix.launcher.activity.LauncherActivity})
D/song    ( 4564): ViewRootImpl---mView.dispatchKeyEvent(event)---KeyCode=82

在开始写这部分的内容时,带着几个疑问开始这段旅程。

1、平时做的定制中经常在 PhoneWindowManager.java 中处理按键的操作,在 InputManagerService.java 中增加组合键的修改 ,还有在 PhoneFallbackEventHandler.java 中处理按键的问题,他们有什么区别 ?
2、为什么老B同事改的那个地方可以解决问题,原理是什么 ?
Android按键事件传递流程(一) —底层上报流程
Android按键事件传递流程(二)—framework层传递流程

2.1、Input输入事件的开始

相关代码:
frameworks/base/core/java/android/view/ViewRootImpl.java

在这里插入图片描述
ViewRootImpl.java 从开始input事件的分发,它有几个内部类,InputStage是抽象基类,ViewPostImeInputStage,EarlyPostImeInputStage,ViewPreImeInputStage等对象都是为了处理输入事件在不同阶段而创建的,比如:ViewPostImeInputStage表示发送输入事件给view树进行处理,这些输入事件都是在输入法处理之后的。ViewPreImeInputStage表示输入事件必须在输入法处理之前发送给view树处理。

ViewPreImeInputStage表示在输入法之前处理,ImeInputStage表示进入输入法处理,ViewPostImeInputStage表示发送给视图。如果有输入法窗口,就先传输给ViewPreImeInputStage处理,如果没有,传输给ViewPostImeInputStage,一般情况下,都是传给ViewPostImeInputStage

ViewPostImeInputStageonProcess方法会被调用,如果是按键事件就调用 processKeyEvent(q) 方法,这个过程的调用流程是: ViewPostImeInputStage.onProcess() --->processKeyEvent(q)-->mView.dispatchKeyEvent

    /**
     * Delivers post-ime input events to the view hierarchy.
     */
    final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }
        @Override
        protected int onProcess(QueuedInputEvent q) {
          //如果是按键事件
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } 
        }

        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;
            if (event.getAction() != KeyEvent.ACTION_UP) {
                // If delivering a new key event, make sure the window is
                // now allowed to start updating.
                handleDispatchDoneAnimating();
            }

            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }

            // If the Control modifier is held, try to interpret the key as a shortcut.
            if (event.getAction() == KeyEvent.ACTION_DOWN
                    && event.isCtrlPressed()
                    && event.getRepeatCount() == 0
                    && !KeyEvent.isModifierKey(event.getKeyCode())) {
                if (mView.dispatchKeyShortcutEvent(event)) {
                    return FINISH_HANDLED;
                }
                if (shouldDropInputEvent(q)) {
                    return FINISH_NOT_HANDLED;
                }
            }

            // Apply the fallback event policy.
            if (mFallbackEventHandler.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }
            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }

            // Handle automatic focus changes.
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                int direction = 0;
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_LEFT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_RIGHT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_UP:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_UP;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_DOWN;
                        }
                        break;
                    case KeyEvent.KEYCODE_TAB:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_FORWARD;
                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                            direction = View.FOCUS_BACKWARD;
                        }
                        break;
                }
                if (direction != 0) {
                    View focused = mView.findFocus();
                    if (focused != null) {
                        View v = focused.focusSearch(direction);
                        if (v != null && v != focused) {
                            // do the math the get the interesting rect
                            // of previous focused into the coord system of
                            // newly focused view
                            focused.getFocusedRect(mTempRect);
                            if (mView instanceof ViewGroup) {
                                ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                        focused, mTempRect);
                                ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                        v, mTempRect);
                            }
                            if (v.requestFocus(direction, mTempRect)) {
                                playSoundEffect(SoundEffectConstants
                                        .getContantForFocusDirection(direction));
                                return FINISH_HANDLED;
                            }
                        }

                        // Give the focused view a last chance to handle the dpad key.
                        if (mView.dispatchUnhandledMove(focused, direction)) {
                            return FINISH_HANDLED;
                        }
                    } else {
                        // find the best view to give focus to in this non-touch-mode with no-focus
                        View v = focusSearch(null, direction);
                        if (v != null && v.requestFocus(direction)) {
                            return FINISH_HANDLED;
                        }
                    }
                }
            }
            return FORWARD;
        }

processKeyEvent() 方法很长,按键事件分发给了mView,这个mView 是PhoneWindow的内部类 DecordView,是所有view的根,从这里就正式将按键事件交给应用层分发了。

// Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

这里我们看到如果按键事件 在 mView.dispatchKeyEvent(event) 没有处理掉,会传递给 mFallbackEventHandler.dispatchKeyEvent(event),mFallbackEventHandler 就是PhoneFallbackEventHandler对象了,这也解释了开头提到的疑问,为什么有的按键事件可以在PhoneFallbackEventHandler.java处理了。

            // Apply the fallback event policy.
            if (mFallbackEventHandler.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

2.2、DecorView的dispatchKeyEvent 处理

相关代码:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java

public DecorView(Context context, int featureId) {
        ...
        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            final int keyCode = event.getKeyCode();
            final int action = event.getAction();
            final boolean isDown = action == KeyEvent.ACTION_DOWN;

            if (isDown && (event.getRepeatCount() == 0)) {
                // First handle chording of panel key: if a panel key is held
                // but not released, try to execute a shortcut in it.
                if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
                    boolean handled = dispatchKeyShortcutEvent(event);
                    if (handled) {
                        return true;
                    }
                }

                // If a panel is open, perform a shortcut on it without the
                // chorded panel key
                if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
                    if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
                        return true;
                    }
                }
            }

            if (!isDestroyed()) {
                final Callback cb = getCallback();
                final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                        : super.dispatchKeyEvent(event);
                if (handled) {
                    return true;
                }
            }

            return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
                    : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
        }

       ...
}

getCallback返回的是CallBack对象cb,cb对象代表一个Activity或Dialog的窗口对象,一般情况下不为空,。
mFeatureId:代表应用程序的特征标识或者整个屏幕的标识,如果是应用程序,就为-1,具体赋值过程为:
Activity的onCreate —-> setContentView —-> PhoneWindow的setContentView —-> installDecor() —->
generateDecor() —-> new DecorView(getContext(), -1)

如果Activity对象为空,或者mFeatureId >=0的话就会执行方法super.dispatchKeyEvent把按键事件给DecorView的父类继续分发,DecorView的父类是FrameLayout,但是它没有dispatchKeyEvent方法,继续找父类就是ViewGroup的dispatchKeyEvent方法,但是ViewGroup没有做任何处理直接抛给了上一级父类就是View了。
这种情况的流程是:DecorView的dispatchKeyEvent–>ViewGroup的dispatchKeyEvent–>View的dispatchKeyEvent

如果Activity对象不为空,mFeatureId为-1,调用Activity对象的dispatchKeyEvent方法。这里面我试过如果在activity中重写dispatchKeyEvent方法并且返回ture话就会停止分发按键事件。

            if (!isDestroyed()) {
                final Callback cb = getCallback();
                final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                        : super.dispatchKeyEvent(event);
                if (handled) {
                    return true;
                }
            }

DecorView的dispatchKeyEvent 处理中会先判断callback (Activity)是否存在并且有没有去处理按键事件,如果activity中没有处理掉按键事件,就会在view 树中一级一级上报,调用super.dispatchKeyEvent(event),即父类的dispatchKeyEvent。
这里提一个疑问:为什么cb就是Activity或者dialog呢 ?请看2.3节

2.3、Activity的dispatchKeyEvent 处理

为什么cb就是Activity或者dialog ?

相关代码:
frameworks/base/core/java/android/app/Activity.java
frameworks/base/core/java/com/android/internal/policy/PolicyManager.java
frameworks/base/core/java/android/view/View.java
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java

我们知道Activity实现了Window.Callback接口,它在setContentView的时候是这么写的:

    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    ···
     //getWindow 方法如下
    public Window getWindow() {
        return mWindow;
    }
    ···
        //在Activity的attach 方法中 有mWindow的初始化操作
        mWindow = PolicyManager.makeNewWindow(this);
        mWindow.setCallback(this);

PolicyManager的makeNewWindow的方法如下,这里不再继续跟进了。这里是通过Binder机制,我们知道Activity是加载在Window上的,而Window是一个抽象类,他的唯一实现类是PhoneWindow,因此我们可以这里说,Activity的视图都是加载在PhoneWindow上的。

  // The static methods to spawn new policy-specific objects
    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }

上面mWindow在初始化的时候就添加了回调mWindow.setCallback(this),所以上述2.2节的callback 我们就可以理解为是Activity !

接下来继续分析**cb.dispatchKeyEvent(event)**的流程:
这部分的调用过程为:Activity的dispatchKeyEvent–>PhoneWindow的superDispatchKeyEvent -->PhoneWindow内部类DecorView 的superDispatchKeyEvent

Activity的dispatchKeyEvent方法

  public boolean dispatchKeyEvent(KeyEvent event) {
        onUserInteraction();

        // Let action bars open menus in response to the menu key prioritized over
        // the window handling it
        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
                mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
            return true;
        }

        Window win = getWindow();
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        View decor = mDecor;
        if (decor == null) decor = win.getDecorView();
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);
    }

PhoneWindow的superDispatchKeyEvent

    @Override
    public boolean superDispatchKeyEvent(KeyEvent event) {
        return mDecor.superDispatchKeyEvent(event);
    }

PhoneWindow 内部类DecorView 的superDispatchKeyEvent

       public boolean superDispatchKeyEvent(KeyEvent event) {
            // Give priority to closing action modes if applicable.
            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                final int action = event.getAction();
                // Back cancels action modes first.
                if (mActionMode != null) {
                    if (action == KeyEvent.ACTION_UP) {
                        mActionMode.finish();
                    }
                    return true;
                }
            }

            return super.dispatchKeyEvent(event);
        }

我们可以看到如果这里没有处理掉按键事件,继续抛给DecorView 的父类,这里的流程不就是
DecorView的dispatchKeyEvent–>ViewGroup的dispatchKeyEvent–>View的dispatchKeyEvent吗,原来和上面的2.2节Activity为null的情况是一样的。DispatchKeyEvent分发流程最后是到了View的DispatchKeyEvent

View 的superDispatchKeyEvent

    public boolean dispatchKeyEvent(KeyEvent event) {
    Log.d("song", "View--dispatchKeyEvent--KeyCode="+event.getKeyCode());
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
        }

        // Give any attached key listener a first crack at the event.
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }

        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

2.4、View的dispatchKeyEvent 处理

相关代码
frameworks/base/core/java/android/view/View.java

如果还是没有被处理,会走到下面:

return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
          : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);

这里面是PhoneWindow的onKeyDown 和onKeyUp方法:

    protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
        /* ****************************************************************************
         * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
         *
         * If your key handling must happen before the app gets a crack at the event,
         * it goes in PhoneWindowManager.
         *
         * If your key handling should happen in all windows, and does not depend on
         * the state of the current application, other than that the current
         * application can override the behavior by handling the event itself, it
         * should go in PhoneFallbackEventHandler.
         *
         * Only if your handling depends on the window, and the fact that it has
         * a DecorView, should it go here.
         * ****************************************************************************/

        final KeyEvent.DispatcherState dispatcher =
                mDecor != null ? mDecor.getKeyDispatcherState() : null;
        //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
        //        + " flags=0x" + Integer.toHexString(event.getFlags()));

        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_DOWN: {
                int direction = keyCode == KeyEvent.KEYCODE_VOLUME_UP ? AudioManager.ADJUST_RAISE
                        : AudioManager.ADJUST_LOWER;
                // If we have a session send it the volume command, otherwise
                // use the suggested stream.
                if (mMediaController != null) {
                    mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
                } else {
                    MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
                            mVolumeControlStreamType, direction,
                            AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
                }
                return true;
            }
            case KeyEvent.KEYCODE_VOLUME_MUTE: {
                getAudioManager().handleKeyDown(event, mVolumeControlStreamType);
                return true;
            }
            // These are all the recognized media key codes in
            // KeyEvent.isMediaKey()
            case KeyEvent.KEYCODE_MEDIA_PLAY:
            case KeyEvent.KEYCODE_MEDIA_PAUSE:
            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
            case KeyEvent.KEYCODE_MUTE:
            case KeyEvent.KEYCODE_HEADSETHOOK:
            case KeyEvent.KEYCODE_MEDIA_STOP:
            case KeyEvent.KEYCODE_MEDIA_NEXT:
            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
            case KeyEvent.KEYCODE_MEDIA_REWIND:
            case KeyEvent.KEYCODE_MEDIA_RECORD:
            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
                if (mMediaController != null) {
                    if (mMediaController.dispatchMediaButtonEvent(event)) {
                        return true;
                    }
                }
                return false;
            }

            case KeyEvent.KEYCODE_MENU: {
                //onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
                return true;
            }

            case KeyEvent.KEYCODE_BACK: {
                if (event.getRepeatCount() > 0) break;
                if (featureId < 0) break;
                // Currently don't do anything with long press.
                if (dispatcher != null) {
                    dispatcher.startTracking(event, this);
                }
                return true;
            }

        }

        return false;
    }

咦,这里面不正是老B同事改的这个地方吗,原理这部分开头提的问题答案就在这里 !!!!

           case KeyEvent.KEYCODE_MENU: {
                //onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
                return true;
            }

这里以这个menu键为例看看这个onKeyDownPanel做了什么。

2.3、onKeyDownPanel 分析

相关代码:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
这部分代码的调用关系是:onKeyDownPanel–>getPanelState–>preparePanel

    public final boolean onKeyDownPanel(int featureId, KeyEvent event) {
        final int keyCode = event.getKeyCode();

        if (event.getRepeatCount() == 0) {
            // The panel key was pushed, so set the chording key
            mPanelChordingKey = keyCode;
            PanelFeatureState st = getPanelState(featureId, false);
            if (st != null && !st.isOpen) {
                return preparePanel(st, event);
            }
        }

        return false;
    }
发布了78 篇原创文章 · 获赞 45 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/An_Times/article/details/89495032
今日推荐