源码角度分析Android的事件输入系统(input system)及ANR原理分析

时间:2024-05-18 22:47:50
此篇我们从android5.0(lolipop)源码角度分析Android的事件输入系统(input system):

先引用一张图来说明下,Android输入系统的整个流程:

源码角度分析Android的事件输入系统(input system)及ANR原理分析
源码角度分析Android的事件输入系统(input system)及ANR原理分析源码角度分析Android的事件输入系统(input system)及ANR原理分析

1、Linux内核,接受输入设备的中断,并将原始事件的数据写入到设备节点中。
2、设备节点,作为内核与IMS的桥梁,它将原始事件的数据暴露给用户空间,以便IMS可以从中读取事件。
3、 InputManagerService,一个Android系统服务,它分为Java层和Native层两部分。Java层负责与WMS的通 信。而Native层则是InputReader和InputDispatcher两个输入系统关键组件的运行容器。
4、EventHub,直接访问所有的设备节点。并且正如其名字所描述的,它通过一个名为getEvents()的函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等。
5、InputReader,I是IMS中的关键组件之一。它运行于一个独立的线程中,负责管理输入设备的列表与配置,以及进行输入事件的加工处理。它通过其线程循环不断地通过getEvents()函数从EventHub中将事件取出并进行处理。对于设备节点的增删事件,它会更新输入设备列表于配置。对于原始输入事件,InputReader对其进行翻译、组装、封装为包含了更多信息、更具可读性的输入事件,然后交给InputDispatcher进行派发。
6、InputReaderPolicy,它为InputReader的事件加工处理提供一些策略配置,例如键盘布局信息等。
7、InputDispatcher,是IMS中的另一个关键组件。它也运行于一个独立的线程中。InputDispatcher中保管了来自WMS的所有窗口的信息,其收到来自InputReader的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口。
8、InputDispatcherPolicy,它为InputDispatcher的派发过程提供策略控制。例如截取某些特定的输入事件用作特殊用途,或者阻止将某些事件派发给目标窗口。一个典型的例子就是HOME键被InputDispatcherPolicy截取到PhoneWindowManager中进行处理,并阻止窗口收到HOME键按下的事件。
9、WMS,虽说不是输入系统中的一员,但是它却对InputDispatcher的正常工作起到了至关重要的作用。当新建窗口时,WMS为新窗口和IMS创建了事件传递所用的通道。另外,WMS还将所有窗口的信息,包括窗口的可点击区域,焦点窗口等信息,实时地更新到IMS的InputDispatcher中,使得InputDispatcher可以正确地将事件派发到指定的窗口。
10、  ViewRootImpl,对于某些窗口,如壁纸窗口、SurfaceView的窗口来说,窗口即是输入事件派发的终点。而对于其他的如Activity、对话框等使用了Android控件系统的窗口来说,输入事件的终点是控件(View)。ViewRootImpl将窗口所接收到的输入事件沿着控件树将事件派发给感兴趣的控件
Linux内核对硬件中断的处理超出了本书的讨论范围,因此本章将以IMS为重点,详细讨论除Linux内核以外的其他参与者的工作原理。

接下来我们就来看看IMS的启动过程,探讨IMS的构成
IMS分为Java层与Native层两个部分
InputManagerService源码位于以下路径:

IMS的主要作用:
它用于管理整个系统的输入部分,包括键盘、鼠标、触摸屏等等。
IMS和其他的系统服务一样,都是在SystemServer中创建的,具体流程如下:
SystemServer中初始化IMS,然后初始化WMS,把IMS作为参数传入
Slog.i(TAG, "Input Manager");
inputManager = new InputManagerService(context);

Slog.i(TAG, "Window Manager");
wm = WindowManagerService.main(context, inputManager,
     mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
     !mFirstBoot, mOnlyCore);
 // 将IMS发布给ServiceManager,以便其他人可以访问IMS提供的接口
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);

mActivityManagerService.setWindowManager(wm);
// 设置向WMS发起回调的callback对象
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
 // 正式启动IMS
inputManager.start();

IMS的诞生分为两个阶段:

·  创建新的IMS对象。

·  调用IMS对象的start()函数完成启动。

接下来看IMS构造函数如下:
public InputManagerService(Context context) {
   this.mContext = context;
   this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());

   mUseDevInputEventForAudioJack =
           context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
   Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
           + mUseDevInputEventForAudioJack);
   mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

   LocalServices.addService(InputManagerInternal.class, new LocalService());
}



构造方法中值得注意的点:获取DisplayThread的looper,说明InputManagerHandler运行在DisplayThread所在线程中
调用c++层的nativeInit方法,传入InputManagerService自己和looper中的messageQueue ;
nativeInit方法如下:
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,  
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {  
    sp<MessageQueue> messageQueue =   
            android_os_MessageQueue_getMessageQueue(env, messageQueueObj);  
    if (messageQueue == NULL) {  
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");  
        return 0;  
    }  

    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,  
            messageQueue->getLooper());  
    im->incStrong(0);  
// 返回了NativeInputManager对象的指针给Java层的IMS,IMS将其保存在mPtr成员变量中
    return reinterpret_cast<jlong>(im);  
}  
由此可见, int 型变量mPtr保存的是NativeInputManager对象指针的地址。
再看NativeInputManager类构造函数
NativeInputManager::NativeInputManager(jobject contextObj,  
        jobject serviceObj, const sp<Looper>& looper) :  
        mLooper(looper), mInteractive(true) {  
    JNIEnv* env = jniEnv();  

    mContextObj = env->NewGlobalRef(contextObj);  
    mServiceObj = env->NewGlobalRef(serviceObj);  

    {  
        AutoMutex _l(mLock);  
        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;  
        mLocked.pointerSpeed = 0;  
        mLocked.pointerGesturesEnabled = true;  
        mLocked.showTouches = false;  
    }  
    mInteractive = true;  
   // 出现重点了, NativeInputManager创建了EventHub
    sp<EventHub> eventHub = new EventHub();  
   // 接着创建了Native层的InputManager
    mInputManager = new InputManager(eventHub, this, this);  
}
在此先简单说下EventHub的作用:EventHub从Input driver中拿事件,是输入设备的控制中心。
接下来看看InputManager的构造方法:
InputManager::InputManager(  
        const sp<EventHubInterface>& eventHub,  
        const sp<InputReaderPolicyInterface>& readerPolicy,  
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {  
    mDispatcher = new InputDispatcher(dispatcherPolicy);  
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);  
    initialize();  
}  
创建了两个对象, InputDispatcher, InputReader:

InputReader负责使用EventHub从Input driver中拿事件,然后让InputMapper解析,接着传给InputDispatcher;
InputDispatcher负责一方面将事件通过InputManager,InputMonitor一路传给PhoneWindowManager来做系统输入事件的处理,另一方面将这些事件传给焦点及监视窗口。

接下来继续看initialize方法:
void InputManager::initialize() {  
    mReaderThread = new InputReaderThread(mReader);  
    mDispatcherThread = new InputDispatcherThread(mDispatcher);  
}  
新建了2个线程对象
接下来回到Java层的inputManager.start()方法:
public void start() {
   Slog.i(TAG, "Starting input manager");
   nativeStart(mPtr);

   // Add ourself to the Watchdog monitors.
   Watchdog.getInstance().addMonitor(this);

   registerPointerSpeedSettingObserver();
   registerShowTouchesSettingObserver();

   mContext.registerReceiver(new BroadcastReceiver() {
       @Override
       public void onReceive(Context context, Intent intent) {
           updatePointerSpeedFromSettings();
           updateShowTouchesFromSettings();
       }
   }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);

   updatePointerSpeedFromSettings();
   updateShowTouchesFromSettings();
}
接着看nativeStart方法实现:
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {  
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);  

    status_t result = im->getInputManager()->start();  
    if (result) {  
        jniThrowRuntimeException(env, "Input manager could not be started.");  
    }  
getInputManager得到的是InputManager对象,接着看start方法:
status_t InputManager::start() {  
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);  
    if (result) {  
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);  
        return result;  
    }  

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);  
    if (result) {  
        ALOGE("Could not start InputReader thread due to error %d.", result);  

        mDispatcherThread->requestExit();  
        return result;  
    }  

    return OK;  
}  
启动mDispatcherThread和mReaderThread线程;
到此阶段,相关类图如下:
源码角度分析Android的事件输入系统(input system)及ANR原理分析

接下来我们分一下事件的读取与加工,以及基础知识INotify与Epoll:
我们先考虑一个问题:如何监控设备节点的新建与删除动作以及如何确定节点中有内容可读呢?最简单的办法是在线程循环中不断地轮询,然而这会导致非常低下的效率,更会导致电量在无谓地轮询中消耗。Android使用由Linux提供的两套机制INotify与Epoll优雅地解决了这两个问题。

INotify介绍与使用

INotify是一个Linux内核所提供的一种文件系统变化通知机制。它可以为应用程序监控文件系统的变化,如文件的新建、删除、读写等。INotify机制有两个基本对象,分别为inotify对象与watch对象,都使用文件描述符表示。

通过INotify机制避免了轮询文件系统的麻烦,但是还有一个问题,INotify机制并不是通过回调的方式通知事件,而需要使用者主动从inotify对象中进行事件读取。那么何时才是读取的最佳时机呢?这就需要借助Linux的另一个优秀的机制Epoll了。


Epoll介绍与使用

无论是从设备节点中获取原始输入事件还是从inotify对象中读取文件系统事件,都面临一个问题,就是这些事件都是偶发的。也就是说,大部分情况下设备节点、inotify对象这些文件描述符中都是无数据可读的,同时又希望有事件到来时可以尽快地对事件作出反应。为解决这个问题,我们不希望不断地轮询这些描述符,也不希望为每个描述符创建一个单独的线程进行阻塞时的读取,因为这都将会导致资源的极大浪费。

此时最佳的办法是使用Epoll机制。Epoll可以使用一次等待监听多个描述符的可读/可写状态。等待返回时携带了可读的描述符或自定义的数据,使用者可以据此读取所需的数据后可以再次进入等待。因此不需要为每个描述符创建独立的线程进行阻塞读取,避免了资源浪费的同时又可以获得较快的响应速度。

相关资料链接:http://blog.****.net/innost/article/details/47660387


好了,接下来我们继续,回到SystemServer中初始化IMS的地方,有一个WindowManagerService(WMS),此类是干什么的呢?

WindowManagerService源码路径:
其实WMS是窗口管理服务,核心维护了一个有序的窗口堆栈,先看一张图:
我们可以基本知道了WMS与IMS之间是有联系的
在介绍WMS之前,首先要了解窗口(Window)是什么:
Android系统中的窗口是屏幕上的一块用于绘制各种UI元素并可以响应应用户输入的一个矩形区域。从原理上来讲,窗口的概念是独自占有一个Surface实例的显示区域。例如Dialog、Activity的界面、壁纸、状态栏以及Toast等都是窗口。
 1、Surface是一块画布,应用可以随心所欲地通过Canvas或者OpenGL在其上作画。
 2、然后通过SurfaceFlinger将多块Surface的内容按照特定的顺序(Z-order)进行混合并输出到FrameBuffer,从而将Android“漂亮的脸蛋”显示给用户。

既然每个窗口都有一块Surface供自己涂鸦,必然需要一个角色对所有窗口的Surface进行协调管理。于是,WMS便应运而生。WMS为所有窗口分配Surface,掌管Surface的显示顺序(Z-order)以及位置尺寸,控制窗口动画,并且还是输入系统的一重要的中转站。

Android输入系统的工作原理概括来说,就是监控/dev/input/下的所有设备节点,当某个节点有数据可读时,将数据读出并进行一系列的翻译加工,然后在所有的窗口中寻找合适的事件接收者,并派发给它。

对Surface的操作类型可以将Android的显示系统分为三个层次,如下图:

源码角度分析Android的事件输入系统(input system)及ANR原理分析

第一个层次是UI框架层,其工作为在Surface上绘制UI元素以及响应输入事件。
第二个层次为WMS,其主要工作在于管理Surface的分配、层级顺序等。
第三层为SurfaceFlinger,负责将多个Surface混合并输出。

WMS的构成:
IMS和其他的系统服务一样,都是在SystemServer中创建的,具体流程如下:
SystemServer中初始化IMS,然后初始化WMS,把IMS作为参数传入
看一下WMS的main()函数的实现:
public static WindowManagerService main(final Context context,
      final InputManagerService im,
      final boolean haveInputMethods, final boolean showBootMsgs,
      final boolean onlyCore) {
  final WindowManagerService[] holder = new WindowManagerService[1];
  DisplayThread.getHandler().runWithScissors(new Runnable() {
      @Override
      public void run() {
          holder[0] = new WindowManagerService(context, im,
                  haveInputMethods, showBootMsgs, onlyCore);
      }
  }, 0);
  return holder[0];
}

看看wms构造方法
    private WindowManagerService(Context context, InputManagerService inputManager,
          boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
...
// 保存InputManagerService。输入事件最终要分发给具有焦点的窗口,而WMS是窗口管理者, 所以WMS是输入系统中的重要一环
mInputManager = inputManager; // Must be before createDisplayContentLocked.
...
      mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
      Display[] displays = mDisplayManager.getDisplays();
      for (Display display : displays) {
          createDisplayContentLocked(display);
      }

...

      //它管理着所有窗口的动画
      mAnimator = new WindowAnimator(this);

      mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
              com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);

      LocalServices.addService(WindowManagerInternal.class, new LocalService());

// 在“UI“线程中将对另一个重要成员mPolicy,也就是WindowManagerPolicy进行初始化
      initPolicy();

      // Add ourself to the Watchdog monitors.
//// 将自己加入到Watchdog中
      Watchdog.getInstance().addMonitor(this);

      SurfaceControl.openTransaction();
      try {
          createWatermarkInTransaction();
          mFocusedStackFrame = new FocusedStackFrame(
                  getDefaultDisplayContentLocked().getDisplay(), mFxSession);
      } finally {
          SurfaceControl.closeTransaction();
      }

      updateCircularDisplayMaskIfNeeded();
      showEmulatorDisplayOverlayIfNeeded();
  }
总结一下在WMS的启动过程中所创建的重要成员:

源码角度分析Android的事件输入系统(input system)及ANR原理分析
对于WMS的详细解读,我打算再下一篇中再说,我们回到IMS中的InputDispatcher与ANR有段的代码段:

在事件的分发过程中,在native层的InputDispatcher类中有一步叫dispatchKeyLocked的方法,此方法中有一个叫
findFocusedWindowTargetsLocked的方法,接下来我们看看此方法的源码:
58int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
    int32_t injectionResult;
    String8 reason;

    // If there is no currently focused window and no focused application
    // then drop the event.
    if (mFocusedWindowHandle == NULL) {
        if (mFocusedApplicationHandle != NULL) {
            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                    mFocusedApplicationHandle, NULL, nextWakeupTime,
                    "Waiting because no window has focus but there is a "
                    "focused application that may eventually add a window "
                    "when it finishes starting up.");
            goto Unresponsive;
        }

        ALOGI("Dropping event because there is no focused window or focused application.");
        injectionResult = INPUT_EVENT_INJECTION_FAILED;
        goto Failed;
    }

    // Check permissions.
    if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) {
        injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
        goto Failed;
    }

    // Check whether the window is ready for more input.
   //检测窗口是否为更多的输入操作而准备就绪
    reason = checkWindowReadyForMoreInputLocked(currentTime,
            mFocusedWindowHandle, entry, "focused");
    if (!reason.isEmpty()) {
        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());
        goto Unresponsive;
    }

    // Success!  Output targets.
    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
   //成功找到目标窗口,添加到目标窗口
    addWindowTargetLocked(mFocusedWindowHandle,
            InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
            inputTargets);

    // Done.
Failed:
Unresponsive:
    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
    updateDispatchStatisticsLocked(currentTime, entry,
            injectionResult, timeSpentWaitingForApplication);
#if DEBUG_FOCUS
    ALOGD("findFocusedWindow finished: injectionResult=%d, "
            "timeSpentWaitingForApplication=%0.1fms",
            injectionResult, timeSpentWaitingForApplication / 1000000.0);
#endif
    return injectionResult;
}

顾名思义,此方法名字就是寻找聚焦窗口
寻找聚焦窗口失败的情况:
无窗口,无应用:Dropping event because there is no focused window or focused application.(这并不导致ANR的情况,因为没有机会调用handleTargetsNotReadyLocked)
无窗口, 有应用:Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.
这就是mokey中抛出此ANR的地方

我们看handleTargetsNotReadyLocked方法:
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
        const EventEntry* entry,
        const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t* nextWakeupTime, const char* reason) {
    if (applicationHandle == NULL && windowHandle == NULL) {
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
    #if DEBUG_FOCUS
            ALOGD("Waiting for system to become ready for input.  Reason: %s", reason);
    #endif
            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
            mInputTargetWaitStartTime = currentTime;
            mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
            mInputTargetWaitTimeoutExpired = false;
            mInputTargetWaitApplicationHandle.clear();
        }
    } else {
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
    #if DEBUG_FOCUS
            ALOGD("Waiting for application to become ready for input: %s.  Reason: %s",
                    getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
                    reason);
    #endif
            nsecs_t timeout;
            if (windowHandle != NULL) {
                timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else if (applicationHandle != NULL) {
                timeout = applicationHandle->getDispatchingTimeout(
                        DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else {
                timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
            }

            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
            mInputTargetWaitStartTime = currentTime;
            mInputTargetWaitTimeoutTime = currentTime + timeout;
            mInputTargetWaitTimeoutExpired = false;
            mInputTargetWaitApplicationHandle.clear();

            if (windowHandle != NULL) {
                mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
            }
            if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
                mInputTargetWaitApplicationHandle = applicationHandle;
            }
        }
    }

    if (mInputTargetWaitTimeoutExpired) {
        return INPUT_EVENT_INJECTION_TIMED_OUT;
    }

    //当超时5s则进入ANR流程
    if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime, reason);

        // Force poll loop to wake up immediately on next iteration once we get the
        // ANR response back from the policy.
        *nextWakeupTime = LONG_LONG_MIN;
        return INPUT_EVENT_INJECTION_PENDING;
    } else {
        // Force poll loop to wake up when timeout is due.
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime;
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

此处mInputTargetWaitTimeoutTime是由当前时间戳+5s, 并设置mInputTargetWaitCause等于INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY. 也就是说ANR时间段是指input等待理由处于INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY(应用没有准备就绪)的时间长达5s的场景.而前面resetANRTimeoutsLocked() 过程是唯一用于重置等待理由的地方.

那么, ANR时间区间是指当前这次的事件dispatch过程中执行findFocusedWindowTargetsLocked()方法到下一次执行resetANRTimeoutsLocked()的时间区间.

    - 当applicationHandle和windowHandle同时为空, 且system准备就绪的情况下
        - 设置等待理由 INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
        - 设置超时等待时长为无限大;
        - 设置TimeoutExpired= false
        - 清空等待队列;
    - 当applicationHandle和windowHandle至少一个不为空, 且application准备就绪的情况下:
        - 设置等待理由 INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
        - 设置超时等待时长为5s;
        - 设置TimeoutExpired= false
        - 清空等待队列;

另外,还有更多多的失败场景见checkWindowReadyForMoreInputLocked的过程,如下:
String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
        const char* targetType) {
    // If the window is paused then keep waiting.
    if (windowHandle->getInfo()->paused) {
        return String8::format("Waiting because the %s window is paused.", targetType);
    }

    // If the window's connection is not registered then keep waiting.
    ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
    if (connectionIndex < 0) {
        return String8::format("Waiting because the %s window's input channel is not "
                "registered with the input dispatcher.  The window may be in the process "
                "of being removed.", targetType);
    }

    // If the connection is dead then keep waiting.
    sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
    if (connection->status != Connection::STATUS_NORMAL) {
        return String8::format("Waiting because the %s window's input connection is %s."
                "The window may be in the process of being removed.", targetType,
                connection->getStatusLabel());
    }

    // If the connection is backed up then keep waiting.
    if (connection->inputPublisherBlocked) {
        return String8::format("Waiting because the %s window's input channel is full.  "
                "Outbound queue length: %d.  Wait queue length: %d.",
                targetType, connection->outboundQueue.count(), connection->waitQueue.count());
    }

    // Ensure that the dispatch queues aren't too far backed up for this event.
    if (eventEntry->type == EventEntry::TYPE_KEY) {
        // If the event is a key event, then we must wait for all previous events to
        // complete before delivering it because previous events may have the
        // side-effect of transferring focus to a different window and we want to
        // ensure that the following keys are sent to the new window.
        //
        // Suppose the user touches a button in a window then immediately presses "A".
        // If the button causes a pop-up window to appear then we want to ensure that
        // the "A" key is delivered to the new pop-up window.  This is because users
        // often anticipate pending UI changes when typing on a keyboard.
        // To obtain this behavior, we must serialize key events with respect to all
        // prior input events.
        if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
            return String8::format("Waiting to send key event because the %s window has not "
                    "finished processing all of the input events that were previously "
                    "delivered to it.  Outbound queue length: %d.  Wait queue length: %d.",
                    targetType, connection->outboundQueue.count(), connection->waitQueue.count());
        }
    } else {
        // Touch events can always be sent to a window immediately because the user intended
        // to touch whatever was visible at the time.  Even if focus changes or a new
        // window appears moments later, the touch event was meant to be delivered to
        // whatever window happened to be on screen at the time.
        //
        // Generic motion events, such as trackball or joystick events are a little trickier.
        // Like key events, generic motion events are delivered to the focused window.
        // Unlike key events, generic motion events don't tend to transfer focus to other
        // windows and it is not important for them to be serialized.  So we prefer to deliver
        // generic motion events as soon as possible to improve efficiency and reduce lag
        // through batching.
        //
        // The one case where we pause input event delivery is when the wait queue is piling
        // up with lots of events because the application is not responding.
        // This condition ensures that ANRs are detected reliably.
        if (!connection->waitQueue.isEmpty()
                && currentTime >= connection->waitQueue.head->deliveryTime
                        + STREAM_AHEAD_EVENT_TIMEOUT) {
            return String8::format("Waiting to send non-key event because the %s window has not "
                    "finished processing certain input events that were delivered to it over "
                    "%0.1fms ago.  Wait queue length: %d.  Wait queue head age: %0.1fms.",
                    targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
                    connection->waitQueue.count(),
                    (currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f);
        }
    }
    return String8::empty();
}