王云四 發表於 2022-11-26 15:34:00

Android系统开发 按键事件的分发详解

<h1><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 128, 128, 1)">前言</span></h1>
<p><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  此篇博客会讲解基于Android10.0系统的按键事件(KeyEvent)分发流程,按键事件包括了设备物理按钮、遥控器、输入法、USB-OTG外接键盘等等。请注意!屏幕上的触控事件不属于按键事件。另外此篇博客不涉及Linux层。</span></p>
<h1><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 128, 128, 1)">大致架构流程</span></h1>
<p><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  在说详解源代码的执行流程前,我们先用最大致的了解下按键事件的流程与设计抽象思维。</span></p>
<p><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">首先要搞清楚,按键事件(KeyEvent)有谁消费。按键事件的消费者其实分2大块,一个是framework层的系统操作消费,一个是View的视图操作消费。</span></p>
<p><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  framework层的系统操作消费一般有:</span></p>
<ul>
<li><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  power键</span></li>
<li><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  音量键</span></li>
<li><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  导航键(back、home等等)</span></li>
<li><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  定制设备的物理键(比如音乐播放/暂停键,快进,倒退,下一步)</span></li>
<li><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  外接键盘上的一些按键</span></li>
</ul>
<p><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  View的视图操作消费一般有:</span></p>
<ul>
<li><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  上下左右方向键(包含遥控器与键盘),用来TV设备上切换焦点</span></li>
<li><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  回车键</span></li>
<li><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">  外接键盘与输入法等等相关其他按键,字母与数字键等等</span></li>
</ul>
<p><span style="background-color: rgba(255, 255, 255, 1); color: rgba(0, 0, 0, 1)">它们都有对应的关键处理类,framework层的系统操作消费对应着</span>PhoneWindowManager(属于系统进程),View的视图操作消费对应着ViewRootImpl(属于应用进程)。</p>
<p>或许还是有一些人不明白,为什么要分2部分来处理按键事件呢?原因其实很简单,这里一一解释:</p>
<p>1.应用与系统的进程不一样,无法将两者结合在一起。&nbsp;进程隔离是Android系统基础中的基础。</p>
<p>2.系统是需要消费按键实现一些系统功能的,比如音量调节、屏幕亮灭、power+音量键截图等等。</p>
<p>3.在应用层上,按键功能是需要传递到应用的UI层进行逻辑处理的。比如方向键与回车键,方向键其实是执行了焦点选择的事件消费,而回车键是执行了点击事件的代码onClickListener。这些都是属于应用层上的事件消费。</p>
<h3><span style="color: rgba(0, 51, 102, 1)">顺序流程</span></h3>
<p><span style="color: rgba(0, 0, 0, 1)">  PhoneWindowManager与ViewRootImpl都消费按键事件,那肯定有一个先后顺序。在代码里PhoneWindowManager是先执行的ViewRootImpl是后执行的。但是有一部分人会认为PhoneWindowManager处理完后会把按键事件转给ViewRootImpl。这是错误的,疏忽一个关键,它们不是一个进程无法直接传递信息,它们也不是互相之间创建了AIDL进行了通信。而是依靠Linux层分发传递了按键事件(反正PhoneWindowManager都是依靠Linux层传上来的,ViewRootImpl在依靠Linux层传上来按键事件也是合情合理的)。所以,Linux层、PhoneWindowManager、ViewRootImpl它们的三者关系如下流程图:</span></p>
<p>&nbsp;<img src="https://img2022.cnblogs.com/blog/1497956/202211/1497956-20221126162209766-384955568.png" alt="" loading="lazy"></p>
<h1><span style="color: rgba(0, 128, 128, 1)">framework层的按键事件消费</span></h1>
<span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(0, 0, 0, 1)">这里插一句题外话,很多framework开发会在</span></span>PhoneWindowManager的interceptKeyBeforeQueueing方法实现自定义的按键功能。比如自定义实现power键的额外功能,或者其他自定义键值的按键功能,也有在这里发送全局广播给应用层app监听后实现功能。这些操作没有问题,但是请额外注意这个变量的&nbsp;int result; 的返回值,因为它关系到按键事件是否分发到应用层。
<p>&nbsp;<img src="https://img2022.cnblogs.com/blog/1497956/202211/1497956-20221126163702515-19381028.png" alt="" loading="lazy"></p>
<h1><span style="color: rgba(0, 128, 128, 1)">应用层的按键事件消费</span></h1>
<p>&nbsp;<img src="https://img2022.cnblogs.com/blog/1497956/202211/1497956-20221126172250241-745171574.png" alt=""></p>
<h2><span style="color: rgba(0, 51, 102, 1)">ViewRootImpl内部的InputStage:</span></h2>
<p><span>InputStage&nbsp;</span>用于实现责任链中某个阶段的基类,处理输入的责任链,在调用deliver时会遍历责任链传递事件。InputStage事件分发完成后会调用finishInputEvent,告知SystemServer进程的InputDispatcher线程,最终将该事件移除,完成此次事件的分发消费。</p>
<p>在ViewRootImpl的setView方法中,完成了InputStage的责任链组装,代码如下:</p>
<p><img src="https://img2022.cnblogs.com/blog/1497956/202211/1497956-20221126173735359-466822086.png" alt="" loading="lazy"></p>
<ul>
<li>SyntheticInputStage。综合处理事件阶段,比如处理导航面板、操作杆等事件。</li>
</ul>
<ul>
<li class="_mce_tagged_br">ViewPostImeInputStage。视图输入处理阶段,比如按键、手指触摸等运动事件,我们熟知的view事件分发就发生在这个阶段。</li>
</ul>
<ul>
<li class="_mce_tagged_br">NativePostImeInputStage。本地方法处理阶段,主要构建了可延迟的队列。</li>
</ul>
<ul>
<li class="_mce_tagged_br">EarlyPostImeInputStage。输入法早期处理阶段。</li>
</ul>
<ul>
<li class="_mce_tagged_br">ImeInputStage。输入法事件处理阶段,处理输入法字符。</li>
</ul>
<ul>
<li class="_mce_tagged_br">ViewPreImeInputStage。视图预处理输入法事件阶段,调用视图view的dispatchKeyEventPreIme方法。</li>
</ul>
<ul>
<li class="_mce_tagged_br">NativePreImeInputStage。本地方法预处理输入法事件阶段。</li>
</ul>
<h2><span>InputStage 的全部代码与继承它的实现类:</span></h2>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Base class for implementing a stage in the chain of responsibility
   * for processing input events.
   * &lt;p&gt;
   * Events are delivered to the stage by the {</span><span style="color: rgba(128, 128, 128, 1)">@link</span><span style="color: rgba(0, 128, 0, 1)"> #deliver} method.The stage
   * then has the choice of finishing the event or forwarding it to the next stage.
   * &lt;/p&gt;
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">abstract</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> InputStage {
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">final</span><span style="color: rgba(0, 0, 0, 1)"> InputStage mNext;

      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> FORWARD = 0<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> FINISH_HANDLED = 1<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> FINISH_NOT_HANDLED = 2<span style="color: rgba(0, 0, 0, 1)">;

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Creates an input stage.
         * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> next The next stage to which events should be forwarded.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> InputStage(InputStage next) {
            mNext </span>=<span style="color: rgba(0, 0, 0, 1)"> next;
      }

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Delivers an event to be processed.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> deliver(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">/ M: Add for monitoring stage status. {</span>
            ViewDebugManager.getInstance().debugInputStageDeliverd(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">,
                  System.currentTimeMillis());
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">/ }</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> ((q.mFlags &amp; QueuedInputEvent.FLAG_FINISHED) != 0<span style="color: rgba(0, 0, 0, 1)">) {
                forward(q);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (shouldDropInputEvent(q)) {
                finish(q, </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                ViewDebugManager.getInstance().debugInputDispatchState(q.mEvent, </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.toString());
                apply(q, onProcess(q));
            }
      }

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Marks the the input event as finished then forwards it to the next stage.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span> finish(QueuedInputEvent q, <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> handled) {
            q.mFlags </span>|=<span style="color: rgba(0, 0, 0, 1)"> QueuedInputEvent.FLAG_FINISHED;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (handled) {
                q.mFlags </span>|=<span style="color: rgba(0, 0, 0, 1)"> QueuedInputEvent.FLAG_FINISHED_HANDLED;
            }
            forward(q);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Forwards the event to the next stage.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> forward(QueuedInputEvent q) {
            onDeliverToNext(q);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Applies a result code from {</span><span style="color: rgba(128, 128, 128, 1)">@link</span><span style="color: rgba(0, 128, 0, 1)"> #onProcess} to the specified event.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span> apply(QueuedInputEvent q, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> result) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result ==<span style="color: rgba(0, 0, 0, 1)"> FORWARD) {
                forward(q);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (result ==<span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED) {
                finish(q, </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (result ==<span style="color: rgba(0, 0, 0, 1)"> FINISH_NOT_HANDLED) {
                finish(q, </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> IllegalArgumentException("Invalid result: " +<span style="color: rgba(0, 0, 0, 1)"> result);
            }
      }

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Called when an event is ready to be processed.
         * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> A result code indicating how the event was handled.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> onProcess(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Called when an event is being delivered to the next stage.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onDeliverToNext(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (DEBUG_INPUT_STAGES) {
                Log.v(mTag, </span>"Done with " + getClass().getSimpleName() + ". " +<span style="color: rgba(0, 0, 0, 1)"> q);
            }
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mNext != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mNext.deliver(q);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                finishInputEvent(q);
            }
      }

      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onWindowFocusChanged(<span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> hasWindowFocus) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mNext != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mNext.onWindowFocusChanged(hasWindowFocus);
            }
      }

      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onDetachedFromWindow() {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mNext != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mNext.onDetachedFromWindow();
            }
      }

      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> shouldDropInputEvent(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mView == <span style="color: rgba(0, 0, 255, 1)">null</span> || !<span style="color: rgba(0, 0, 0, 1)">mAdded) {
                Slog.w(mTag, </span>"Dropping event due to root view being removed: " +<span style="color: rgba(0, 0, 0, 1)"> q.mEvent);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((!<span style="color: rgba(0, 0, 0, 1)">mAttachInfo.mHasWindowFocus
                  </span>&amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
                  </span>&amp;&amp; !isAutofillUiShowing()) ||<span style="color: rgba(0, 0, 0, 1)"> mStopped
                  </span>|| (mIsAmbientMode &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON))
                  </span>|| (mPausedForTransition &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">isBack(q.mEvent))) {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This is a focus event and the window doesn't currently have input focus or
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> has stopped. This could be an event that came back from the previous stage
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> but the window has lost focus or stopped in the meantime.</span>
                <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isTerminalInputEvent(q.mEvent)) {
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Don't drop terminal input events, however mark them as canceled.</span>
<span style="color: rgba(0, 0, 0, 1)">                  q.mEvent.cancel();
                  Slog.w(mTag, </span>"Cancelling event due to no window focus: " +<span style="color: rgba(0, 0, 0, 1)"> q.mEvent);
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
                }

                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Drop non-terminal input events.</span>
                Slog.w(mTag, "Dropping event due to no window focus: " +<span style="color: rgba(0, 0, 0, 1)"> q.mEvent);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> dump(String prefix, PrintWriter writer) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mNext != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mNext.dump(prefix, writer);
            }
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> isBack(InputEvent event) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> ((KeyEvent) event).getKeyCode() ==<span style="color: rgba(0, 0, 0, 1)"> KeyEvent.KEYCODE_BACK;
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
            }
      }
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Base class for implementing an input pipeline stage that supports
   * asynchronous and out-of-order processing of input events.
   * &lt;p&gt;
   * In addition to what a normal input stage can do, an asynchronous
   * input stage may also defer an input event that has been delivered to it
   * and finish or forward it later.
   * &lt;/p&gt;
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">abstract</span> <span style="color: rgba(0, 0, 255, 1)">class</span> AsyncInputStage <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> InputStage {
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">final</span><span style="color: rgba(0, 0, 0, 1)"> String mTraceCounter;

      </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> QueuedInputEvent mQueueHead;
      </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> QueuedInputEvent mQueueTail;
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> mQueueLength;

      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> DEFER = 3<span style="color: rgba(0, 0, 0, 1)">;

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Creates an asynchronous input stage.
         * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> next The next stage to which events should be forwarded.
         * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> traceCounter The name of a counter to record the size of
         * the queue of pending events.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> AsyncInputStage(InputStage next, String traceCounter) {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(next);
            mTraceCounter </span>=<span style="color: rgba(0, 0, 0, 1)"> traceCounter;
      }

      </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
         * Marks the event as deferred, which is to say that it will be handled
         * asynchronously.The caller is responsible for calling {</span><span style="color: rgba(128, 128, 128, 1)">@link</span><span style="color: rgba(0, 128, 0, 1)"> #forward}
         * or {</span><span style="color: rgba(128, 128, 128, 1)">@link</span><span style="color: rgba(0, 128, 0, 1)"> #finish} later when it is done handling the event.
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> defer(QueuedInputEvent q) {
            q.mFlags </span>|=<span style="color: rgba(0, 0, 0, 1)"> QueuedInputEvent.FLAG_DEFERRED;
            enqueue(q);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> forward(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Clear the deferred flag.</span>
            q.mFlags &amp;= ~<span style="color: rgba(0, 0, 0, 1)">QueuedInputEvent.FLAG_DEFERRED;

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Fast path if the queue is empty.</span>
            QueuedInputEvent curr =<span style="color: rgba(0, 0, 0, 1)"> mQueueHead;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (curr == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.forward(q);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Determine whether the event must be serialized behind any others
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> before it can be delivered to the next stage.This is done because
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> deferred events might be handled out of order by the stage.</span>
            <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> deviceId =<span style="color: rgba(0, 0, 0, 1)"> q.mEvent.getDeviceId();
            QueuedInputEvent prev </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">boolean</span> blocked = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">while</span> (curr != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; curr !=<span style="color: rgba(0, 0, 0, 1)"> q) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!blocked &amp;&amp; deviceId ==<span style="color: rgba(0, 0, 0, 1)"> curr.mEvent.getDeviceId()) {
                  blocked </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
                }
                prev </span>=<span style="color: rgba(0, 0, 0, 1)"> curr;
                curr </span>=<span style="color: rgba(0, 0, 0, 1)"> curr.mNext;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If the event is blocked, then leave it in the queue to be delivered later.
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Note that the event might not yet be in the queue if it was not previously
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> deferred so we will enqueue it if needed.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (blocked) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (curr == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                  enqueue(q);
                }
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> The event is not blocked.Deliver it immediately.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (curr != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                curr </span>=<span style="color: rgba(0, 0, 0, 1)"> curr.mNext;
                dequeue(q, prev);
            }
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.forward(q);

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Dequeuing this event may have unblocked successors.Deliver them.</span>
            <span style="color: rgba(0, 0, 255, 1)">while</span> (curr != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (deviceId ==<span style="color: rgba(0, 0, 0, 1)"> curr.mEvent.getDeviceId()) {
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> ((curr.mFlags &amp; QueuedInputEvent.FLAG_DEFERRED) != 0<span style="color: rgba(0, 0, 0, 1)">) {
                        </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
                  }
                  QueuedInputEvent next </span>=<span style="color: rgba(0, 0, 0, 1)"> curr.mNext;
                  dequeue(curr, prev);
                  </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.forward(curr);
                  curr </span>=<span style="color: rgba(0, 0, 0, 1)"> next;
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                  prev </span>=<span style="color: rgba(0, 0, 0, 1)"> curr;
                  curr </span>=<span style="color: rgba(0, 0, 0, 1)"> curr.mNext;
                }
            }
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span> apply(QueuedInputEvent q, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> result) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result ==<span style="color: rgba(0, 0, 0, 1)"> DEFER) {
                defer(q);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.apply(q, result);
            }
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> enqueue(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mQueueTail == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mQueueHead </span>=<span style="color: rgba(0, 0, 0, 1)"> q;
                mQueueTail </span>=<span style="color: rgba(0, 0, 0, 1)"> q;
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                mQueueTail.mNext </span>=<span style="color: rgba(0, 0, 0, 1)"> q;
                mQueueTail </span>=<span style="color: rgba(0, 0, 0, 1)"> q;
            }

            mQueueLength </span>+= 1<span style="color: rgba(0, 0, 0, 1)">;
            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> dequeue(QueuedInputEvent q, QueuedInputEvent prev) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (prev == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mQueueHead </span>=<span style="color: rgba(0, 0, 0, 1)"> q.mNext;
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                prev.mNext </span>=<span style="color: rgba(0, 0, 0, 1)"> q.mNext;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mQueueTail ==<span style="color: rgba(0, 0, 0, 1)"> q) {
                mQueueTail </span>=<span style="color: rgba(0, 0, 0, 1)"> prev;
            }
            q.mNext </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;

            mQueueLength </span>-= 1<span style="color: rgba(0, 0, 0, 1)">;
            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> dump(String prefix, PrintWriter writer) {
            writer.print(prefix);
            writer.print(getClass().getName());
            writer.print(</span>": mQueueLength="<span style="color: rgba(0, 0, 0, 1)">);
            writer.println(mQueueLength);

            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.dump(prefix, writer);
      }
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Delivers pre-ime input events to a native activity.
   * Does not support pointer events.
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span> NativePreImeInputStage <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> AsyncInputStage
            </span><span style="color: rgba(0, 0, 255, 1)">implements</span><span style="color: rgba(0, 0, 0, 1)"> InputQueue.FinishedInputEventCallback {
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> NativePreImeInputStage(InputStage next, String traceCounter) {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(next, traceCounter);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> onProcess(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mInputQueue != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; q.mEvent <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent) {
                mInputQueue.sendInputEvent(q.mEvent, q, </span><span style="color: rgba(0, 0, 255, 1)">true</span>, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> DEFER;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onFinishedInputEvent(Object token, <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> handled) {
            QueuedInputEvent q </span>=<span style="color: rgba(0, 0, 0, 1)"> (QueuedInputEvent)token;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (handled) {
                finish(q, </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            forward(q);
      }
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Delivers pre-ime input events to the view hierarchy.
   * Does not support pointer events.
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span> ViewPreImeInputStage <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> InputStage {
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ViewPreImeInputStage(InputStage next) {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(next);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> onProcess(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (q.mEvent <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> processKeyEvent(q);
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> processKeyEvent(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">final</span> KeyEvent event =<span style="color: rgba(0, 0, 0, 1)"> (KeyEvent)q.mEvent;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mView.dispatchKeyEventPreIme(event)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Delivers input events to the ime.
   * Does not support pointer events.
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span> ImeInputStage <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> AsyncInputStage
            </span><span style="color: rgba(0, 0, 255, 1)">implements</span><span style="color: rgba(0, 0, 0, 1)"> InputMethodManager.FinishedInputEventCallback {
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ImeInputStage(InputStage next, String traceCounter) {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(next, traceCounter);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> onProcess(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mLastWasImTarget &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">isInLocalFocusMode()) {
                InputMethodManager imm </span>= mContext.getSystemService(InputMethodManager.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (imm != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                  </span><span style="color: rgba(0, 0, 255, 1)">final</span> InputEvent event =<span style="color: rgba(0, 0, 0, 1)"> q.mEvent;
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " +<span style="color: rgba(0, 0, 0, 1)"> event);
                  </span><span style="color: rgba(0, 0, 255, 1)">int</span> result = imm.dispatchInputEvent(event, q, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">, mHandler);
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result ==<span style="color: rgba(0, 0, 0, 1)"> InputMethodManager.DISPATCH_HANDLED) {
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
                  } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (result ==<span style="color: rgba(0, 0, 0, 1)"> InputMethodManager.DISPATCH_NOT_HANDLED) {
                        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> The IME could not handle it, so skip along to the next InputStage</span>
                        <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
                  } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span> DEFER; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> callback will be invoked later</span>
<span style="color: rgba(0, 0, 0, 1)">                  }
                }
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onFinishedInputEvent(Object token, <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> handled) {
            QueuedInputEvent q </span>=<span style="color: rgba(0, 0, 0, 1)"> (QueuedInputEvent)token;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (DEBUG_IMF || ViewDebugManager.DEBUG_INPUT ||<span style="color: rgba(0, 0, 0, 1)"> ViewDebugManager.DEBUG_KEY) {
                Log.d(mTag, </span>"IME finishedEvent: handled = " + handled + ", event = " +<span style="color: rgba(0, 0, 0, 1)"> q
                        </span>+ ", viewAncestor = " + <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
            }
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (handled) {
                finish(q, </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            forward(q);
      }
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Performs early processing of post-ime input events.
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span> EarlyPostImeInputStage <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> InputStage {
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> EarlyPostImeInputStage(InputStage next) {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(next);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> onProcess(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (q.mEvent <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> processKeyEvent(q);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (q.mEvent <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> MotionEvent) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> processMotionEvent(q);
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> processKeyEvent(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">final</span> KeyEvent event =<span style="color: rgba(0, 0, 0, 1)"> (KeyEvent)q.mEvent;

            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mAttachInfo.mTooltipHost != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mAttachInfo.mTooltipHost.handleTooltipKey(event);
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If the key's purpose is to exit touch mode then we consume it
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> and consider it handled.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (checkForLeavingTouchModeAndConsume(event)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Make sure the fallback event policy sees all keys that will be
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> delivered to the view hierarchy.</span>
<span style="color: rgba(0, 0, 0, 1)">            mFallbackEventHandler.preDispatchKeyEvent(event);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> processMotionEvent(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">final</span> MotionEvent event =<span style="color: rgba(0, 0, 0, 1)"> (MotionEvent) q.mEvent;

            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> processPointerEvent(q);
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If the motion event is from an absolute position device, exit touch mode</span>
            <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> action =<span style="color: rgba(0, 0, 0, 1)"> event.getActionMasked();
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (action == MotionEvent.ACTION_DOWN || action ==<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_SCROLL) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.isFromSource(InputDevice.SOURCE_CLASS_POSITION)) {
                  ensureTouchMode(</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
                }
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> processPointerEvent(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">final</span> MotionEvent event =<span style="color: rgba(0, 0, 0, 1)"> (MotionEvent)q.mEvent;

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Translate the pointer event for compatibility, if needed.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (mTranslator != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mTranslator.translateEventInScreenToAppWindow(event);
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Enter touch mode on down or scroll, if it is coming from a touch screen device,
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> exit otherwise.</span>
            <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> action =<span style="color: rgba(0, 0, 0, 1)"> event.getAction();
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (action == MotionEvent.ACTION_DOWN || action ==<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_SCROLL) {
                ensureTouchMode(event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
            }

            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (action ==<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_DOWN) {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Upon motion event within app window, close autofill ui.</span>
                AutofillManager afm =<span style="color: rgba(0, 0, 0, 1)"> getAutofillManager();
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (afm != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                  afm.requestHideFillUi();
                }
            }

            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (action == MotionEvent.ACTION_DOWN &amp;&amp; mAttachInfo.mTooltipHost != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mAttachInfo.mTooltipHost.hideTooltip();
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Offset the scroll position.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (mCurScrollY != 0<span style="color: rgba(0, 0, 0, 1)">) {
                event.offsetLocation(</span>0<span style="color: rgba(0, 0, 0, 1)">, mCurScrollY);
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Remember the touch position for possible drag-initiation.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.isTouchEvent()) {
                mLastTouchPoint.x </span>=<span style="color: rgba(0, 0, 0, 1)"> event.getRawX();
                mLastTouchPoint.y </span>=<span style="color: rgba(0, 0, 0, 1)"> event.getRawY();
                mLastTouchSource </span>=<span style="color: rgba(0, 0, 0, 1)"> event.getSource();
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Delivers post-ime input events to a native activity.
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span> NativePostImeInputStage <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> AsyncInputStage
            </span><span style="color: rgba(0, 0, 255, 1)">implements</span><span style="color: rgba(0, 0, 0, 1)"> InputQueue.FinishedInputEventCallback {
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> NativePostImeInputStage(InputStage next, String traceCounter) {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(next, traceCounter);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> onProcess(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mInputQueue != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                mInputQueue.sendInputEvent(q.mEvent, q, </span><span style="color: rgba(0, 0, 255, 1)">false</span>, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> DEFER;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onFinishedInputEvent(Object token, <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> handled) {
            QueuedInputEvent q </span>=<span style="color: rgba(0, 0, 0, 1)"> (QueuedInputEvent)token;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (handled) {
                finish(q, </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            forward(q);
      }
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Delivers post-ime input events to the view hierarchy.
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span> ViewPostImeInputStage <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> InputStage {
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ViewPostImeInputStage(InputStage next) {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(next);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> onProcess(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (q.mEvent <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> processKeyEvent(q);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> source =<span style="color: rgba(0, 0, 0, 1)"> q.mEvent.getSource();
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> ((source &amp; InputDevice.SOURCE_CLASS_POINTER) != 0<span style="color: rgba(0, 0, 0, 1)">) {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> processPointerEvent(q);
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((source &amp; InputDevice.SOURCE_CLASS_TRACKBALL) != 0<span style="color: rgba(0, 0, 0, 1)">) {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> processTrackballEvent(q);
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> processGenericMotionEvent(q);
                }
            }
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onDeliverToNext(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mUnbufferedInputDispatch
                  </span>&amp;&amp; q.mEvent <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> MotionEvent
                  </span>&amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> ((MotionEvent)q.mEvent).isTouchEvent()
                  </span>&amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> isTerminalInputEvent(q.mEvent)) {
                mUnbufferedInputDispatch </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
                scheduleConsumeBatchedInput();
            }
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onDeliverToNext(q);
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> performFocusNavigation(KeyEvent event) {
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> direction = 0<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (event.getKeyCode()) {
                </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent.KEYCODE_DPAD_LEFT:
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.hasNoModifiers()) {
                        direction </span>=<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_LEFT;
                  }
                  </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
                </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent.KEYCODE_DPAD_RIGHT:
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.hasNoModifiers()) {
                        direction </span>=<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_RIGHT;
                  }
                  </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
                </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent.KEYCODE_DPAD_UP:
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.hasNoModifiers()) {
                        direction </span>=<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_UP;
                  }
                  </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
                </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent.KEYCODE_DPAD_DOWN:
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.hasNoModifiers()) {
                        direction </span>=<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_DOWN;
                  }
                  </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
                </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> KeyEvent.KEYCODE_TAB:
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.hasNoModifiers()) {
                        direction </span>=<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_FORWARD;
                  } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                        direction </span>=<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_BACKWARD;
                  }
                  </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (direction != 0<span style="color: rgba(0, 0, 0, 1)">) {
                View focused </span>=<span style="color: rgba(0, 0, 0, 1)"> mView.findFocus();
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (focused != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                  View v </span>=<span style="color: rgba(0, 0, 0, 1)"> focused.focusSearch(direction);
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (v != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; v !=<span style="color: rgba(0, 0, 0, 1)"> focused) {
                        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> do the math the get the interesting rect
                        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> of previous focused into the coord system of
                        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> newly focused view</span>
<span style="color: rgba(0, 0, 0, 1)">                        focused.getFocusedRect(mTempRect);
                        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mView <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> ViewGroup) {
                            ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                    focused, mTempRect);
                            ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                    v, mTempRect);
                        }
                        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (v.requestFocus(direction, mTempRect)) {
                            playSoundEffect(SoundEffectConstants
                                    .getContantForFocusDirection(direction));
                            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
                        }
                  }

                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Give the focused view a last chance to handle the dpad key.</span>
                  <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mView.dispatchUnhandledMove(focused, direction)) {
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
                  }
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mView.restoreDefaultFocus()) {
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
                  }
                }
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> performKeyboardGroupNavigation(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> direction) {
            </span><span style="color: rgba(0, 0, 255, 1)">final</span> View focused =<span style="color: rgba(0, 0, 0, 1)"> mView.findFocus();
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (focused == <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> mView.restoreDefaultFocus()) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            View cluster </span>= focused == <span style="color: rgba(0, 0, 255, 1)">null</span> ? keyboardNavigationClusterSearch(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, direction)
                  : focused.keyboardNavigationClusterSearch(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, direction);

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Since requestFocus only takes "real" focus directions (and therefore also
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN.</span>
            <span style="color: rgba(0, 0, 255, 1)">int</span> realDirection =<span style="color: rgba(0, 0, 0, 1)"> direction;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (direction == View.FOCUS_FORWARD || direction ==<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_BACKWARD) {
                realDirection </span>=<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_DOWN;
            }

            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cluster != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> cluster.isRootNamespace()) {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> the default cluster. Try to find a non-clustered view to focus.</span>
                <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (cluster.restoreFocusNotInCluster()) {
                  playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
                }
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> otherwise skip to next actual cluster</span>
                cluster = keyboardNavigationClusterSearch(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, direction);
            }

            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cluster != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> cluster.restoreFocusInCluster(realDirection)) {
                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> processKeyEvent(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">final</span> KeyEvent event =<span style="color: rgba(0, 0, 0, 1)"> (KeyEvent)q.mEvent;

            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mUnhandledKeyManager.preViewDispatch(event)) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (ViewDebugManager.DEBUG_ENG) {
                  Log.v(mTag, </span>"App handle dispatchUnique event = " + event + ", mView = " +<span style="color: rgba(0, 0, 0, 1)"> mView
                            </span>+ ", this = " + <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
                }

                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Deliver the key to the view hierarchy.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mView.dispatchKeyEvent(event)) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (ViewDebugManager.DEBUG_ENG) {
                  Log.v(mTag, </span>"App handle key event: event = " + event + ", mView = " +<span style="color: rgba(0, 0, 0, 1)"> mView
                            </span>+ ", this = " + <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
                }
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
            }

            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (shouldDropInputEvent(q)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_NOT_HANDLED;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This dispatch is for windows that don't have a Window.Callback. Otherwise,
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> the Window.Callback usually will have already called this (see
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> DecorView.superDispatchKeyEvent) leaving this call a no-op.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mUnhandledKeyManager.dispatch(mView, event)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
            }

            </span><span style="color: rgba(0, 0, 255, 1)">int</span> groupNavigationDirection = 0<span style="color: rgba(0, 0, 0, 1)">;

            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.getAction() ==<span style="color: rgba(0, 0, 0, 1)"> KeyEvent.ACTION_DOWN
                  </span>&amp;&amp; event.getKeyCode() ==<span style="color: rgba(0, 0, 0, 1)"> KeyEvent.KEYCODE_TAB) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
                  groupNavigationDirection </span>=<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_FORWARD;
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (KeyEvent.metaStateHasModifiers(event.getMetaState(),
                        KeyEvent.META_META_ON </span>|<span style="color: rgba(0, 0, 0, 1)"> KeyEvent.META_SHIFT_ON)) {
                  groupNavigationDirection </span>=<span style="color: rgba(0, 0, 0, 1)"> View.FOCUS_BACKWARD;
                }
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If a modifier is held, try to interpret the key as a shortcut.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (event.getAction() ==<span style="color: rgba(0, 0, 0, 1)"> KeyEvent.ACTION_DOWN
                  </span>&amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">KeyEvent.metaStateHasNoModifiers(event.getMetaState())
                  </span>&amp;&amp; event.getRepeatCount() == 0
                  &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">KeyEvent.isModifierKey(event.getKeyCode())
                  </span>&amp;&amp; groupNavigationDirection == 0<span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mView.dispatchKeyShortcutEvent(event)) {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
                }
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (shouldDropInputEvent(q)) {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_NOT_HANDLED;
                }
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Apply the fallback event policy.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mFallbackEventHandler.dispatchKeyEvent(event)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (shouldDropInputEvent(q)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_NOT_HANDLED;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Handle automatic focus changes.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (event.getAction() ==<span style="color: rgba(0, 0, 0, 1)"> KeyEvent.ACTION_DOWN) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (groupNavigationDirection != 0<span style="color: rgba(0, 0, 0, 1)">) {
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (performKeyboardGroupNavigation(groupNavigationDirection)) {
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
                  }
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (performFocusNavigation(event)) {
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
                  }
                }
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> processPointerEvent(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">final</span> MotionEvent event =<span style="color: rgba(0, 0, 0, 1)"> (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
            mAttachInfo.mHandlingPointerEvent </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">boolean</span> handled =<span style="color: rgba(0, 0, 0, 1)"> mView.dispatchPointerEvent(event);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (handled &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> ViewDebugManager.DEBUG_ENG) {
                Log.v(mTag, </span>"App handle pointer event: event = " +<span style="color: rgba(0, 0, 0, 1)"> event
                           </span>+ ", mView = " +<span style="color: rgba(0, 0, 0, 1)"> mView
                           </span>+ ", this = " + <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
            }
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mAttachInfo.mUnbufferedDispatchRequested &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mConsumeBatchedInputScheduled) {
                  scheduleConsumeBatchedInputImmediately();
                }
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> handled ?<span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED : FORWARD;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> maybeUpdatePointerIcon(MotionEvent event) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.getPointerCount() == 1 &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> event.isFromSource(InputDevice.SOURCE_MOUSE)) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.getActionMasked() ==<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_HOVER_ENTER
                        </span>|| event.getActionMasked() ==<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_HOVER_EXIT) {
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Other apps or the window manager may change the icon type outside of
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this app, therefore the icon type has to be reset on enter/exit event.</span>
                  mPointerIconType =<span style="color: rgba(0, 0, 0, 1)"> PointerIcon.TYPE_NOT_SPECIFIED;
                }

                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.getActionMasked() !=<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_HOVER_EXIT) {
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!updatePointerIcon(event) &amp;&amp;<span style="color: rgba(0, 0, 0, 1)">
                            event.getActionMasked() </span>==<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_HOVER_MOVE) {
                        mPointerIconType </span>=<span style="color: rgba(0, 0, 0, 1)"> PointerIcon.TYPE_NOT_SPECIFIED;
                  }
                }
            }
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> processTrackballEvent(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">final</span> MotionEvent event =<span style="color: rgba(0, 0, 0, 1)"> (MotionEvent)q.mEvent;

            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!hasPointerCapture() ||<span style="color: rgba(0, 0, 0, 1)"> mView.dispatchCapturedPointerEvent(event)) {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
                }
            }

            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mView.dispatchTrackballEvent(event)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> processGenericMotionEvent(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">final</span> MotionEvent event =<span style="color: rgba(0, 0, 0, 1)"> (MotionEvent)q.mEvent;

            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (event.isFromSource(InputDevice.SOURCE_TOUCHPAD)) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (hasPointerCapture() &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> mView.dispatchCapturedPointerEvent(event)) {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
                }
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Deliver the event to the view.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mView.dispatchGenericMotionEvent(event)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> resetPointerIcon(MotionEvent event) {
      mPointerIconType </span>=<span style="color: rgba(0, 0, 0, 1)"> PointerIcon.TYPE_NOT_SPECIFIED;
      updatePointerIcon(event);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> updatePointerIcon(MotionEvent event) {
      </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> pointerIndex = 0<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">float</span> x =<span style="color: rgba(0, 0, 0, 1)"> event.getX(pointerIndex);
      </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">float</span> y =<span style="color: rgba(0, 0, 0, 1)"> event.getY(pointerIndex);
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mView == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> E.g. click outside a popup to dismiss it</span>
            Slog.d(mTag, "updatePointerIcon called after view was removed"<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (x &lt; 0 || x &gt;= mView.getWidth() || y &lt; 0 || y &gt;=<span style="color: rgba(0, 0, 0, 1)"> mView.getHeight()) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> E.g. when moving window divider with mouse</span>
            Slog.d(mTag, "updatePointerIcon called with position out of bounds"<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">final</span> PointerIcon pointerIcon =<span style="color: rgba(0, 0, 0, 1)"> mView.onResolvePointerIcon(event, pointerIndex);
      </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> pointerType = (pointerIcon != <span style="color: rgba(0, 0, 255, 1)">null</span>) ?<span style="color: rgba(0, 0, 0, 1)">
                pointerIcon.getType() : PointerIcon.TYPE_DEFAULT;

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mPointerIconType !=<span style="color: rgba(0, 0, 0, 1)"> pointerType) {
            mPointerIconType </span>=<span style="color: rgba(0, 0, 0, 1)"> pointerType;
            mCustomPointerIcon </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mPointerIconType !=<span style="color: rgba(0, 0, 0, 1)"> PointerIcon.TYPE_CUSTOM) {
                InputManager.getInstance().setPointerIconType(pointerType);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }
      }
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mPointerIconType == PointerIcon.TYPE_CUSTOM &amp;&amp;
                !<span style="color: rgba(0, 0, 0, 1)">pointerIcon.equals(mCustomPointerIcon)) {
            mCustomPointerIcon </span>=<span style="color: rgba(0, 0, 0, 1)"> pointerIcon;
            InputManager.getInstance().setCustomPointerIcon(mCustomPointerIcon);
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> maybeUpdateTooltip(MotionEvent event) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.getPointerCount() != 1<span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> action =<span style="color: rgba(0, 0, 0, 1)"> event.getActionMasked();
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (action !=<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_HOVER_ENTER
                </span>&amp;&amp; action !=<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_HOVER_MOVE
                </span>&amp;&amp; action !=<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_HOVER_EXIT) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      AccessibilityManager manager </span>=<span style="color: rgba(0, 0, 0, 1)"> AccessibilityManager.getInstance(mContext);
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (manager.isEnabled() &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> manager.isTouchExplorationEnabled()) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mView == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            Slog.d(mTag, </span>"maybeUpdateTooltip called after view was removed"<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      mView.dispatchTooltipHoverEvent(event);
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Performs synthesis of new input events from unhandled input events.
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span> SyntheticInputStage <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> InputStage {
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">final</span> SyntheticTrackballHandler mTrackball = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SyntheticTrackballHandler();
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">final</span> SyntheticJoystickHandler mJoystick = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SyntheticJoystickHandler();
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">final</span> SyntheticTouchNavigationHandler mTouchNavigation =
                <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SyntheticTouchNavigationHandler();
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">final</span> SyntheticKeyboardHandler mKeyboard = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SyntheticKeyboardHandler();

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> SyntheticInputStage() {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span>(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> onProcess(QueuedInputEvent q) {
            q.mFlags </span>|=<span style="color: rgba(0, 0, 0, 1)"> QueuedInputEvent.FLAG_RESYNTHESIZED;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (q.mEvent <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> MotionEvent) {
                </span><span style="color: rgba(0, 0, 255, 1)">final</span> MotionEvent event =<span style="color: rgba(0, 0, 0, 1)"> (MotionEvent)q.mEvent;
                </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> source =<span style="color: rgba(0, 0, 0, 1)"> event.getSource();
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> ((source &amp; InputDevice.SOURCE_CLASS_TRACKBALL) != 0<span style="color: rgba(0, 0, 0, 1)">) {
                  mTrackball.process(event);
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((source &amp; InputDevice.SOURCE_CLASS_JOYSTICK) != 0<span style="color: rgba(0, 0, 0, 1)">) {
                  mJoystick.process(event);
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((source &amp;<span style="color: rgba(0, 0, 0, 1)"> InputDevice.SOURCE_TOUCH_NAVIGATION)
                        </span>==<span style="color: rgba(0, 0, 0, 1)"> InputDevice.SOURCE_TOUCH_NAVIGATION) {
                  mTouchNavigation.process(event);
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
                }
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((q.mFlags &amp; QueuedInputEvent.FLAG_UNHANDLED) != 0<span style="color: rgba(0, 0, 0, 1)">) {
                mKeyboard.process((KeyEvent)q.mEvent);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FINISH_HANDLED;
            }

            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FORWARD;
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onDeliverToNext(QueuedInputEvent q) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> ((q.mFlags &amp; QueuedInputEvent.FLAG_RESYNTHESIZED) == 0<span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Cancel related synthetic events if any prior stage has handled the event.</span>
                <span style="color: rgba(0, 0, 255, 1)">if</span> (q.mEvent <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> MotionEvent) {
                  </span><span style="color: rgba(0, 0, 255, 1)">final</span> MotionEvent event =<span style="color: rgba(0, 0, 0, 1)"> (MotionEvent)q.mEvent;
                  </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> source =<span style="color: rgba(0, 0, 0, 1)"> event.getSource();
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> ((source &amp; InputDevice.SOURCE_CLASS_TRACKBALL) != 0<span style="color: rgba(0, 0, 0, 1)">) {
                        mTrackball.cancel();
                  } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((source &amp; InputDevice.SOURCE_CLASS_JOYSTICK) != 0<span style="color: rgba(0, 0, 0, 1)">) {
                        mJoystick.cancel();
                  } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((source &amp;<span style="color: rgba(0, 0, 0, 1)"> InputDevice.SOURCE_TOUCH_NAVIGATION)
                            </span>==<span style="color: rgba(0, 0, 0, 1)"> InputDevice.SOURCE_TOUCH_NAVIGATION) {
                        mTouchNavigation.cancel(event);
                  }
                }
            }
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onDeliverToNext(q);
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onWindowFocusChanged(<span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> hasWindowFocus) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">hasWindowFocus) {
                mJoystick.cancel();
            }
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onDetachedFromWindow() {
            mJoystick.cancel();
      }
    }</span></pre>
</div>
<h2><span style="color: rgba(0, 51, 102, 1)">ViewRootImpl继续分发到DecorView</span></h2>
<p>&nbsp;一部分事件会在各色InputStage的责任链中消费掉,如果在这些责任链中没有消费掉的事件会继续向上传递给DecorView,向DecorView传递的主要责任链是ViewPostImeInputStage。在ViewRootImpl中的mView就是DecorView,只要看到mView.dispatch这些关键字的代码基本上可以认为是在分发各种事件到DecorView,在ViewPostImeInputStage里分发的主要事件如下:</p>
<ul>
<li>mView.dispatchPointerEvent(event);&nbsp; 分发触控事件,在processPointerEvent方法里</li>
<li>mView.dispatchKeyEvent(event)&nbsp; 分发按键事件,在processKeyEvent方法里</li>
<li>mView.dispatchKeyShortcutEvent(event) 分发快捷键事件 ,在processKeyEvent方法里</li>
<li>mView.dispatchCapturedPointerEvent(event)&nbsp; 分发轨迹球指针捕获事件(特别老式的轨迹球设备),在processTrackballEvent方法里</li>
<li>mView.dispatchTrackballEvent(event) 分发轨迹球事件,在processTrackballEvent方法里</li>
<li>mView.dispatchCapturedPointerEvent(event)&nbsp; 分发鼠标指针捕获事件,在processGenericMotionEvent方法里</li>
<li>mView.dispatchGenericMotionEvent(event)&nbsp;&nbsp;分发鼠标事件,在processGenericMotionEvent方法里</li>
</ul>
<p>这里是继续向DecorView分发按键事件的代码片段</p>
<p>&nbsp;<img src="https://img2022.cnblogs.com/blog/1497956/202211/1497956-20221126191506536-12453745.png" alt="" loading="lazy"></p>
<h2><span style="color: rgba(0, 51, 102, 1)">继续分发到View层</span></h2>
<p><span style="color: rgba(0, 51, 102, 1)">如下经过了</span>DecorView -&gt; Activity -&gt;&nbsp;PhoneWindow -&gt;&nbsp;DecorView -&gt;&nbsp;ViewGroup -&gt;&nbsp;View.</p>
<p>另外插一句,可以举一反三其实触控事件分发也是这个顺序。</p>
<h3><span style="color: rgba(0, 51, 102, 1)">第一步,DecorView中的按键分发:</span></h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> dispatchKeyEvent(KeyEvent event) {
    </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> keyCode =<span style="color: rgba(0, 0, 0, 1)"> event.getKeyCode();
    </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> action =<span style="color: rgba(0, 0, 0, 1)"> event.getAction();
    </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> isDown = action ==<span style="color: rgba(0, 0, 0, 1)"> KeyEvent.ACTION_DOWN;

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (isDown &amp;&amp; (event.getRepeatCount() == 0<span style="color: rgba(0, 0, 0, 1)">)) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> First handle chording of panel key: if a panel key is held
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> but not released, try to execute a shortcut in it.</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> ((mWindow.mPanelChordingKey &gt; 0) &amp;&amp; (mWindow.mPanelChordingKey !=<span style="color: rgba(0, 0, 0, 1)"> keyCode)) {
            </span><span style="color: rgba(0, 0, 255, 1)">boolean</span> handled =<span style="color: rgba(0, 0, 0, 1)"> dispatchKeyShortcutEvent(event);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (handled) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If a panel is open, perform a shortcut on it without the
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> chorded panel key</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> ((mWindow.mPreparedPanel != <span style="color: rgba(0, 0, 255, 1)">null</span>) &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> mWindow.mPreparedPanel.isOpen) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0<span style="color: rgba(0, 0, 0, 1)">)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">mWindow.isDestroyed()) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">cb其实就是对应的Activity/Dialog,这里分发给了Activity</span>
      <span style="color: rgba(0, 0, 255, 1)">final</span> Window.Callback cb =<span style="color: rgba(0, 0, 0, 1)"> mWindow.getCallback();
      </span><span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> handled = cb != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; mFeatureId &lt; 0 ?<span style="color: rgba(0, 0, 0, 1)"> cb.dispatchKeyEvent(event)
                : </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.dispatchKeyEvent(event);
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (handled) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span> isDown ?<span style="color: rgba(0, 0, 0, 1)"> mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
            : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}</span></pre>
</div>
<h3><span style="color: rgba(0, 51, 102, 1)">第二步,Activity的按键分发</span></h3>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * Called to process key events.You can override this to intercept all
   * key events before they are dispatched to the window.Be sure to call
   * this implementation for key events that should be handled normally.
   *
   * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> event The key event.
   *
   * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> boolean Return true if this event was consumed.
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> dispatchKeyEvent(KeyEvent event) {
      onUserInteraction();

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Let action bars open menus in response to the menu key prioritized over
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> the window handling it</span>
      <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> keyCode =<span style="color: rgba(0, 0, 0, 1)"> event.getKeyCode();
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (keyCode == KeyEvent.KEYCODE_MENU &amp;&amp;<span style="color: rgba(0, 0, 0, 1)">
                mActionBar </span>!= <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> mActionBar.onMenuKeyEvent(event)) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }

      Window win </span>=<span style="color: rgba(0, 0, 0, 1)"> getWindow();
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (win.superDispatchKeyEvent(event)) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这里分发给了PhoneWindow</span>
            <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      View decor </span>=<span style="color: rgba(0, 0, 0, 1)"> mDecor;
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (decor == <span style="color: rgba(0, 0, 255, 1)">null</span>) decor =<span style="color: rgba(0, 0, 0, 1)"> win.getDecorView();
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> event.dispatch(<span style="color: rgba(0, 0, 255, 1)">this</span>, decor != <span style="color: rgba(0, 0, 255, 1)">null</span>
                ? decor.getKeyDispatcherState() : <span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
    }</span></pre>
</div>
<h3><span style="color: rgba(0, 51, 102, 1)">第三步,PhoneWindow的按键分发</span></h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> superDispatchKeyEvent(KeyEvent event) {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> mDecor.superDispatchKeyEvent(event); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这里继续分发给了DecorView</span>
    }</pre>
</div>
<h3><span style="color: rgba(0, 51, 102, 1)">第四步,DecorView的按键再分发,这里回头分发给DecorView,是为了DecorView与Activity的解耦,因为Activity只持有Window。 另外你可以看到上面Window的分发代码就是转个手而已</span></h3>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> superDispatchKeyEvent(KeyEvent event) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Give priority to closing action modes if applicable.</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果是back键这里会直接消费掉</span>
            <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> action =<span style="color: rgba(0, 0, 0, 1)"> event.getAction();
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Back cancels action modes first.</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (mPrimaryActionMode != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (action ==<span style="color: rgba(0, 0, 0, 1)"> KeyEvent.ACTION_UP) {
                  mPrimaryActionMode.finish();
                }
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }
      }

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">super</span>.dispatchKeyEvent(event)) {<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这里继续分发给ViewGroup</span>
            <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">return</span> (getViewRootImpl() != <span style="color: rgba(0, 0, 255, 1)">null</span>) &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> getViewRootImpl().dispatchUnhandledKeyEvent(event);
    }</span></pre>
</div>
<h3><span style="color: rgba(0, 51, 102, 1)">第五步,ViewGroup的按键分发</span></h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> dispatchKeyEvent(KeyEvent event) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mInputEventConsistencyVerifier != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            mInputEventConsistencyVerifier.onKeyEvent(event, </span>1<span style="color: rgba(0, 0, 0, 1)">);
      }

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> ((mPrivateFlags &amp; (PFLAG_FOCUSED |<span style="color: rgba(0, 0, 0, 1)"> PFLAG_HAS_BOUNDS))
                </span>== (PFLAG_FOCUSED |<span style="color: rgba(0, 0, 0, 1)"> PFLAG_HAS_BOUNDS)) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">super</span>.dispatchKeyEvent(event)) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这里会继续分发给View</span>
                <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (mFocused != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; (mFocused.mPrivateFlags &amp;<span style="color: rgba(0, 0, 0, 1)"> PFLAG_HAS_BOUNDS)
                </span>==<span style="color: rgba(0, 0, 0, 1)"> PFLAG_HAS_BOUNDS) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mFocused.dispatchKeyEvent(event)) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这个是分发ViewGroup给有焦点的子View</span>
                <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }
      }

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mInputEventConsistencyVerifier != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, </span>1<span style="color: rgba(0, 0, 0, 1)">);
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
    }</span></pre>
</div>
<h3><span style="color: rgba(0, 51, 102, 1)">第六步,View的按键分发,到终点了</span></h3>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> dispatchKeyEvent(KeyEvent event) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mInputEventConsistencyVerifier != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            mInputEventConsistencyVerifier.onKeyEvent(event, </span>0<span style="color: rgba(0, 0, 0, 1)">);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Give any attached key listener a first crack at the event.
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">noinspection SimplifiableIfStatement</span>
      ListenerInfo li =<span style="color: rgba(0, 0, 0, 1)"> mListenerInfo;
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (li != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; li.mOnKeyListener != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; (mViewFlags &amp; ENABLED_MASK) ==<span style="color: rgba(0, 0, 0, 1)"> ENABLED
                </span>&amp;&amp; li.mOnKeyListener.onKey(<span style="color: rgba(0, 0, 255, 1)">this</span>, event.getKeyCode(), event)) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这里已经回调到mOnKeyListener了,我们应用层就可以接收到按键键值了</span>
            <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.dispatch(<span style="color: rgba(0, 0, 255, 1)">this</span>, mAttachInfo != <span style="color: rgba(0, 0, 255, 1)">null</span>
                ? mAttachInfo.mKeyDispatchState : <span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mInputEventConsistencyVerifier != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, </span>0<span style="color: rgba(0, 0, 0, 1)">);
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
    }</span></pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>End</p>

</div>
<div id="MySignature" role="contentinfo">
    <div style="text-align: center">
    <p style="color:orange;font-size:16px;" >本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/16927533.html </p>
    <div style="color:orange;font-size:16px;">本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。 </div>
</div><br><br>
来源:https://www.cnblogs.com/guanxinjing/p/16927533.html
頁: [1]
查看完整版本: Android系统开发 按键事件的分发详解