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.应用与系统的进程不一样,无法将两者结合在一起。 进程隔离是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> <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监听后实现功能。这些操作没有问题,但是请额外注意这个变量的 int result; 的返回值,因为它关系到按键事件是否分发到应用层。
<p> <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> <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 </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.
* <p>
* 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.
* </p>
</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 & 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>&& !<span style="color: rgba(0, 0, 0, 1)">q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
</span>&& !isAutofillUiShowing()) ||<span style="color: rgba(0, 0, 0, 1)"> mStopped
</span>|| (mIsAmbientMode && !<span style="color: rgba(0, 0, 0, 1)">q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON))
</span>|| (mPausedForTransition && !<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.
* <p>
* 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.
* </p>
</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 &= ~<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> && curr !=<span style="color: rgba(0, 0, 0, 1)"> q) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!blocked && 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 & 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> && 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 && !<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 && 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 & 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 & 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>&& 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, 0, 1)"> ((MotionEvent)q.mEvent).isTouchEvent()
</span>&&<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> && 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> &&<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> &&<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> &&<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>&& 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>&& !<span style="color: rgba(0, 0, 0, 1)">KeyEvent.metaStateHasNoModifiers(event.getMetaState())
</span>&& event.getRepeatCount() == 0
&& !<span style="color: rgba(0, 0, 0, 1)">KeyEvent.isModifierKey(event.getKeyCode())
</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)"> (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 &&<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 && !<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 &&<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) &&<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() &&<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 < 0 || x >= mView.getWidth() || y < 0 || y >=<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 &&
!<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>&& action !=<span style="color: rgba(0, 0, 0, 1)"> MotionEvent.ACTION_HOVER_MOVE
</span>&& 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() &&<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 & 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 & 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 &<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 & 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 & 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 & 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 & 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 &<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> 一部分事件会在各色InputStage的责任链中消费掉,如果在这些责任链中没有消费掉的事件会继续向上传递给DecorView,向DecorView传递的主要责任链是ViewPostImeInputStage。在ViewRootImpl中的mView就是DecorView,只要看到mView.dispatch这些关键字的代码基本上可以认为是在分发各种事件到DecorView,在ViewPostImeInputStage里分发的主要事件如下:</p>
<ul>
<li>mView.dispatchPointerEvent(event); 分发触控事件,在processPointerEvent方法里</li>
<li>mView.dispatchKeyEvent(event) 分发按键事件,在processKeyEvent方法里</li>
<li>mView.dispatchKeyShortcutEvent(event) 分发快捷键事件 ,在processKeyEvent方法里</li>
<li>mView.dispatchCapturedPointerEvent(event) 分发轨迹球指针捕获事件(特别老式的轨迹球设备),在processTrackballEvent方法里</li>
<li>mView.dispatchTrackballEvent(event) 分发轨迹球事件,在processTrackballEvent方法里</li>
<li>mView.dispatchCapturedPointerEvent(event) 分发鼠标指针捕获事件,在processGenericMotionEvent方法里</li>
<li>mView.dispatchGenericMotionEvent(event) 分发鼠标事件,在processGenericMotionEvent方法里</li>
</ul>
<p>这里是继续向DecorView分发按键事件的代码片段</p>
<p> <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 -> Activity -> PhoneWindow -> DecorView -> ViewGroup -> 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 && (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 > 0) && (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>) &&<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> && mFeatureId < 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 &&<span style="color: rgba(0, 0, 0, 1)">
mActionBar </span>!= <span style="color: rgba(0, 0, 255, 1)">null</span> &&<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>) &&<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 & (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> && (mFocused.mPrivateFlags &<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> && li.mOnKeyListener != <span style="color: rgba(0, 0, 255, 1)">null</span> && (mViewFlags & ENABLED_MASK) ==<span style="color: rgba(0, 0, 0, 1)"> ENABLED
</span>&& 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> </p>
<p> </p>
<p> </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]