You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.2 KiB

ANR detection in InputDispatcher

'ANR' means 'application not responding'. This is an event that gets triggered when the system thinks that an application is too slow to respond. A dialog may pop up because of an ANR event. ANRs can be triggered by multiple systems within Android.

In InputDispatcher, ANRs are raised in 2 cases:

  1. An event was sent to a connection, and the response was not received within a certain timeout.
  2. The application did not have a focused window, and an input event that requires focus was generated by the user.

Let's consider each of these cases.

1. Application does not respond to an input event that was sent to it.

The most common case is when an application does not respond to input that dispatcher sent to it. Typically, it means an application is performing a long operation on its UI thread.

When the event is being dispatched to an application, the normal flow is: mPendingEventconnection.outboundQueueconnection.waitQueue.

Every dispatch cycle, InputDispatcher will check all connections to see if any are unresponsive. To determine whether an app is not responding, we look at the oldest entry in the waitQueue. If the entry sits in the waitQueue past entry.timeoutTime, we trigger an ANR.

Checking if a connection is unresponsive

When a dispatch entry is sent to the app, its deliveryTime and timeoutTime fields are populated. The deliveryTime is the time that the event is delivered to the app. This is simply the current time inside publishMotionEvent. The timeoutTime is the time when this entry would be considered overdue. At that time, the ANR process would start for this connection.

Most connections are associated with a window, and each window may have a custom timeout time. To calculate the timeout time of a specific event, simply add the window.dispatchingTimeout to the current time. In case where there is no associated window, such as gesture monitors, use the default dispatching timeout which is defined in android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS.

The timeoutTime field of the DispatchEntry is needed because the window associated with a specific connection may change its timeout time. Therefore, entries sent prior to the timeout change would need to follow the previous timeout value. If a window timeout changes, it only affects new events being dispatched, and does not alter the timeout times of events already sent to the app.

For example, if an application is being debugged, the ActivityManager may want to increase the timeout time for a window to prevent the ANR dialog from appearing or the app from getting killed.

Looping through waitQueues of all connections on every dispatch cycle could be costly. To improve this, we introduced the AnrTracker class.

AnrTracker uses a multiset (a set that allows duplicate entries) to keep track of the next time a dispatch entry would become out of date. Duplicate entries are allowed because there may be two events with an identical timeout time. This is unlikely to happen in practice today, but is possible if the window timeouts are different or if the device has a high input report rate or a low clock resolution.

On each dispatch cycle, InputDispatcher checks AnrTracker for the nearest timeout value. If the nearest timeout value is in the past, InputDispatcher will trigger the ANR for the corresponding connection.

When an application sends a response for a particular dispatch entry, that entry is removed from the connection's waitQueue, and it is also removed from the AnrTracker. During normal operation, the entries are removed from AnrTracker quickly.

How to test

In order to test this behaviour, you can create an application that calls SystemClock.sleep while handling a click event.

When this happens, the expectation is that the ANR dialog will come up within a short period of sending an input event (typically 5 seconds). While the app is not responding, it is expected that touches on other applications and gesture monitors still continue to work.

2. Application does not have a focused window, and a focused event comes in

This is a legacy behaviour that we are maintaining inside InputDispatcher. When an application is launched, WindowManager calls setFocusedApplication to tell InputDispatcher that there is a focused application. This is used by InputDispatcher purely for ANR and debugging purposes.

After launching, an application may not add a focused window. This could be either due to a bug in WindowManager or in the app.

The legacy behaviour in this situation is as follows: touches will continue to function normally, without causing an ANR. If there is a focused event, however, it would require a focused window to be dispatched. InputDispatcher will keep this focused event inside mPendingEvent until:

  • A focused window is added
  • Timeout occurs
  • User touches another application

To keep track of this timeout, when this situation is detected initially, mInputTargetWaitTimeoutTime and mAwaitedFocusedApplication are set. When the mInputTargetWaitTimeoutTime expires, an ANR will be raised.

How to test

Create an empty application that sets FLAG_NOT_FOCUSABLE on its window in onCreate. Touching the application's window should not cause an ANR. Sending a key event to the application, however, should cause ANR. One easy way to do this is by pressing or gesturing the BACK key. In this scenario, adb shell dumpsys input will reveal that there's no focused window in the current display.

Extending the timeout based on the response from policy

When the policy processes the ANR notification and responds with a positive timeout, InputDispatcher marks the connection as "responsive" by setting inputPublisherBlocked = false. All of the entries for this connection inside AnrTracker will be modified to expire at time = (current time) + (timeout extension returned by policy).

If the policy wants to abort dispatch, it returns a timeout value of 0. In this case, InputDispatcher will synthesize cancel events for the connection.

When an app is unresponsive, new touches do not go to the app. They get dropped with a warning log. This is done to prevent overwhelming the app with events in case it later becomes responsive.