Merge \"Report all content changes to a11y services.\" into nyc-dev
am: 8ac5bcbab2

Change-Id: I9338fd3ef749f80c4b0252c7c749fe6e395b3c0a
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7da969f..60d3339 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2228,7 +2228,8 @@
             @Override
             public void handleMessage(Message message) {
                 final int eventType =  message.what;
-                notifyAccessibilityEventInternal(eventType);
+                AccessibilityEvent event = (AccessibilityEvent) message.obj;
+                notifyAccessibilityEventInternal(eventType, event);
             }
         };
 
@@ -3130,16 +3131,22 @@
                 // be modified to remove its source if the receiving service does
                 // not have permission to access the window content.
                 AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
-                AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
-                mPendingEvents.put(eventType, newEvent);
-
-                final int what = eventType;
-                if (oldEvent != null) {
-                    mEventDispatchHandler.removeMessages(what);
-                    oldEvent.recycle();
+                Message message;
+                if ((mNotificationTimeout > 0)
+                        && (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {
+                    // Allow at most one pending event
+                    final AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
+                    mPendingEvents.put(eventType, newEvent);
+                    if (oldEvent != null) {
+                        mEventDispatchHandler.removeMessages(eventType);
+                        oldEvent.recycle();
+                    }
+                    message = mEventDispatchHandler.obtainMessage(eventType);
+                } else {
+                    // Send all messages, bypassing mPendingEvents
+                    message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
                 }
 
-                Message message = mEventDispatchHandler.obtainMessage(what);
                 mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
             }
         }
@@ -3149,9 +3156,8 @@
          *
          * @param eventType The type of the event to dispatch.
          */
-        private void notifyAccessibilityEventInternal(int eventType) {
+        private void notifyAccessibilityEventInternal(int eventType, AccessibilityEvent event) {
             IAccessibilityServiceClient listener;
-            AccessibilityEvent event;
 
             synchronized (mLock) {
                 listener = mServiceInterface;
@@ -3162,28 +3168,32 @@
                     return;
                 }
 
-                event = mPendingEvents.get(eventType);
-
-                // Check for null here because there is a concurrent scenario in which this
-                // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
-                // which posts a message for dispatching an event. 2) The message is pulled
-                // from the queue by the handler on the service thread and the latter is
-                // just about to acquire the lock and call this method. 3) Now another binder
-                // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked
-                // so the service thread waits for the lock; 4) The binder thread replaces
-                // the event with a more recent one (assume the same event type) and posts a
-                // dispatch request releasing the lock. 5) Now the main thread is unblocked and
-                // dispatches the event which is removed from the pending ones. 6) And ... now
-                // the service thread handles the last message posted by the last binder call
-                // but the event is already dispatched and hence looking it up in the pending
-                // ones yields null. This check is much simpler that keeping count for each
-                // event type of each service to catch such a scenario since only one message
-                // is processed at a time.
+                // There are two ways we notify for events, throttled and non-throttled. If we
+                // are not throttling, then messages come with events, which we handle with
+                // minimal fuss.
                 if (event == null) {
-                    return;
+                    // We are throttling events, so we'll send the event for this type in
+                    // mPendingEvents as long as it it's null. It can only null due to a race
+                    // condition:
+                    //
+                    //   1) A binder thread calls notifyAccessibilityServiceDelayedLocked
+                    //      which posts a message for dispatching an event and stores the event
+                    //      in mPendingEvents.
+                    //   2) The message is pulled from the queue by the handler on the service
+                    //      thread and this method is just about to acquire the lock.
+                    //   3) Another binder thread acquires the lock in notifyAccessibilityEvent
+                    //   4) notifyAccessibilityEvent recycles the event that this method was about
+                    //      to process, replaces it with a new one, and posts a second message
+                    //   5) This method grabs the new event, processes it, and removes it from
+                    //      mPendingEvents
+                    //   6) The second message dispatched in (4) arrives, but the event has been
+                    //      remvoved in (5).
+                    event = mPendingEvents.get(eventType);
+                    if (event == null) {
+                        return;
+                    }
+                    mPendingEvents.remove(eventType);
                 }
-
-                mPendingEvents.remove(eventType);
                 if (mSecurityPolicy.canRetrieveWindowContentLocked(this)) {
                     event.setConnectionId(mId);
                 } else {