AndroidにおけるkeyEventのメッセージ処理

14959 ワード

1. ViewRootImpl.deliverKeyEvent(QueuedInputEvent q)
      1. mViewが空またはmAddedがfalseの場合、finishInputEventが直接呼び出されます.
      2. mView.dispatchKeyEventPreIme(event)は、IMEに渡す前にいくつかの前処理を行う.ビューでは、入力ウィンドウが存在する場合、キーメッセージが入力ウィンドウに送信されます.入力ウィンドウがこのイベントを処理していない場合にのみ、本物のビューに送信されます.したがって、入力方式でイベントを切り取る前にメッセージを処理する場合は、この方法を再ロードして特定のキーメッセージを処理することができる.
      3. IMEウィンドウがあれば、これをIMEに渡して処理します.imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
      4. deliverKeyEventPostIme(q); メッセージを本物のビューに送信します.
    private void deliverKeyEvent(QueuedInputEvent q) {
        final KeyEvent event = (KeyEvent)q.mEvent;
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
        }

        if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
            // If there is no view, then the event will not be handled.
            if (mView == null || !mAdded) {
                finishInputEvent(q, false);
                return;
            }

            if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);

            // Perform predispatching before the IME.
            if (mView.dispatchKeyEventPreIme(event)) {
                finishInputEvent(q, true);
                return;
            }

            // Dispatch to the IME before propagating down the view hierarchy.
            // The IME will eventually call back into handleImeFinishedEvent.
            if (mLastWasImTarget) {
                InputMethodManager imm = InputMethodManager.peekInstance();
                if (imm != null) {
                    final int seq = event.getSequenceNumber();
                    if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
                            + seq + " event=" + event);
                    imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
                    return;
                }
            }
        }

        // Not dispatching to IME, continue with post IME actions.
        deliverKeyEventPostIme(q);
    }

1.2  ViewRootImpl.deliverKeyEventPostIme(q);
         1. mView.dispatchKeyEvent(event); イベントをビューに送信します.mViewはDecorViewなので、DecorViewのdispatchKeyEventを呼び出す方法です.
    private void deliverKeyEventPostIme(QueuedInputEvent q) {
        final KeyEvent event = (KeyEvent)q.mEvent;

        ... ...
        // If the key's purpose is to exit touch mode then we consume it and consider it handled.
        if (checkForLeavingTouchModeAndConsume(event)) {
            finishInputEvent(q, true);
            return;
        }

        // Make sure the fallback event policy sees all keys that will be delivered to the
        // view hierarchy.
        mFallbackEventHandler.preDispatchKeyEvent(event);

        // Deliver the key to the view hierarchy.
        if (mView.dispatchKeyEvent(event)) {
            finishInputEvent(q, true);
            return;
        }

        // 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)) {
                finishInputEvent(q, true);
                return;
            }
        }

        // Apply the fallback event policy.
        if (mFallbackEventHandler.dispatchKeyEvent(event)) {
            finishInputEvent(q, true);
            return;
        }

        // 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));
                            finishInputEvent(q, true);
                            return;
                        }
                    }

                    // Give the focused view a last chance to handle the dpad key.
                    if (mView.dispatchUnhandledMove(focused, direction)) {
                        finishInputEvent(q, true);
                        return;
                    }
                }
            }
        }

        // Key was unhandled.
        finishInputEvent(q, false);
    }

1.2.1 DecorView.dispatchKeyEvent(KeyEvent event)
     1. Downイベントの場合は、まずShortcutイベント、すなわちショートカットボタンまたは特殊イベントかどうかを判断し、dispatchKeyShortcutEventとperformPanelShortcutを呼び出していくつかの処理を行い、戻ってくる.
     2. ショートカットボタンでない場合はCallback Activityのcbを呼び出す.dispatchKeyEvent(event)で処理します.ActivityのdispatchKeyEventでもPhoneWindowのsuperDispatchKeyEventを呼び出し、PhoneWindowは結局mDecorを呼び出す.superDispatchKeyEvent(event);
        @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);
        }

1.2.1.2 DecorView.mDecor.superDispatchKeyEvent(event);
           1. 親クラスのdispatchKeyEventを呼び出します.
       public boolean superDispatchKeyEvent(KeyEvent event) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }

            // Not handled by the view hierarchy, does the action bar want it
            // to cancel out of something special?
            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;
                }

                // Next collapse any expanded action views.
                if (mActionBar != null && mActionBar.hasExpandedActionView()) {
                    if (action == KeyEvent.ACTION_UP) {
                        mActionBar.collapseActionView();
                    }
                    return true;
                }
            }

            return false;
        }

1.2.1.2.1 ViewGroup.dispatchKeyEvent(KeyEvent event)
        1. mPrivateFlagsは、現在のViewのプロパティを示すビューで定義されています.ここかなFOCUSEDとHAS_BOUNDSは、現在のViewがfocusedでboundsがあるかどうかを示します.一般的なビューとビューグループにはHAS_がありますBOUNDS、mFocusedは、現在のViewGroupのfocusのViewを記録します.
例えば、AがBを含む場合、BがCを含む場合、CがViewである場合、CのmPrivateFlagsはFOCUSED|HAS_であるBOUNDSではBのmPrivateFlagsがFOCUSED、mFocusedがC、同理AのmPrivateFlagsもFOCUSED、mFocusedがBです.
        2. だから一般的には、ViewGroupはmFocusedを通過します.dispatchKeyEvent()は、EventをViewに再帰的に伝えます.
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
        }

        if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }

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

1.2.1.2.1.2 View.dispatchKeyEvent()
mFocusedがView Groupの場合、1.2.1.2.1が呼び出されます.Viewの場合は、ViewのdispatchKeyEventを呼び出します.
            1. Listenerが登録されている場合は、ListenerのonKeyを直接呼び出して対応させ、返します.
            2. Listenerが登録されていない場合はKeyEventを呼び出す.dispatchは最後の処理をします
    public boolean dispatchKeyEvent(KeyEvent event) {
        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;
    }

1.2.1.2.1.2.2 KeyEvent.dispatch
        1. KeyEvent.dispatchはActionのタイプに応じてreceiverの対応するonXXX関数を呼び出して処理します.(receiverは通常View)
        2. すべてのViewがこのKeyEventを処理していない場合、ActivityのonXXX関数は最終的にKeyEventによって呼び出されます.
    public final boolean dispatch(Callback receiver, DispatcherState state,
            Object target) {
        switch (mAction) {
            case ACTION_DOWN: {
                mFlags &= ~FLAG_START_TRACKING;
                if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
                        + ": " + this);
                boolean res = receiver.onKeyDown(mKeyCode, this);
                if (state != null) {
                    if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
                        if (DEBUG) Log.v(TAG, "  Start tracking!");
                        state.startTracking(this, target);
                    } else if (isLongPress() && state.isTracking(this)) {
                        try {
                            if (receiver.onKeyLongPress(mKeyCode, this)) {
                                if (DEBUG) Log.v(TAG, "  Clear from long press!");
                                state.performedLongPress(this);
                                res = true;
                            }
                        } catch (AbstractMethodError e) {
                        }
                    }
                }
                return res;
            }
            case ACTION_UP:
                if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
                        + ": " + this);
                if (state != null) {
                    state.handleUpEvent(this);
                }
                return receiver.onKeyUp(mKeyCode, this);
            case ACTION_MULTIPLE:
                final int count = mRepeatCount;
                final int code = mKeyCode;
                if (receiver.onKeyMultiple(code, count, this)) {
                    return true;
                }
                if (code != KeyEvent.KEYCODE_UNKNOWN) {
                    mAction = ACTION_DOWN;
                    mRepeatCount = 0;
                    boolean handled = receiver.onKeyDown(code, this);
                    if (handled) {
                        mAction = ACTION_UP;
                        receiver.onKeyUp(code, this);
                    }
                    mAction = ACTION_MULTIPLE;
                    mRepeatCount = count;
                    return handled;
                }
                return false;
        }
        return false;
    }