Touch exploration feature, event bubling, refactor

1. Added an Input Filter that interprets the touch screen motion
   events to perfrom accessibility exploration. One finger explores.
   Tapping within a given time and distance slop on the last exlopred
   location does click and long press, respectively. Two fingers close
   and in the same diretion drag. Multiple finglers or two fingers in
   different directions or two fingers too far away are delegated to
   the view hierarchy. Non moving fingers "accidentally grabbed the
   device for the scrren" are ignored.

2. Added accessibility events for hover enter, hover exit, touch
   exoloration gesture start, and end. Accessibility hover events
   are fired by the hover pipeline. An accessibility event is
   dispatched up the view tree and the topmost view fires it.
   Thus predecessors can augment the fired event. An accessibility
   event has several records and a predecessor can optionally
   modify, delete, and add such to the event.

3. Added onPopulateAccessibilityEvent and refactored the existing
   accessibility code to use it.

4. Added API for querying the currently enabled accessibility services
   by feedback type.

Change-Id: Iea2258c07ffae9491071825d966dc453b07e5134
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index b5ca2c2..e14b975 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -30,7 +30,6 @@
  * @hide
  */
 public final class InputEventConsistencyVerifier {
-    private static final String TAG = "InputEventConsistencyVerifier";
     private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);
 
     // The number of recent events to log when a problem is detected.
@@ -44,6 +43,11 @@
     // Consistency verifier flags.
     private final int mFlags;
 
+    // Tag for logging which a client can set to help distinguish the output
+    // from different verifiers since several can be active at the same time.
+    // If not provided defaults to the simple class name.
+    private final String mLogTag;
+
     // The most recently checked event and the nesting level at which it was checked.
     // This is only set when the verifier is called from a nesting level greater than 0
     // so that the verifier can detect when it has been asked to verify the same event twice.
@@ -103,8 +107,19 @@
      * @param flags Flags to the verifier, or 0 if none.
      */
     public InputEventConsistencyVerifier(Object caller, int flags) {
+        this(caller, flags, InputEventConsistencyVerifier.class.getSimpleName());
+    }
+
+    /**
+     * Creates an input consistency verifier.
+     * @param caller The object to which the verifier is attached.
+     * @param flags Flags to the verifier, or 0 if none.
+     * @param logTag Tag for logging. If null defaults to the short class name.
+     */
+    public InputEventConsistencyVerifier(Object caller, int flags, String logTag) {
         this.mCaller = caller;
         this.mFlags = flags;
+        this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier";
     }
 
     /**
@@ -596,7 +611,7 @@
                 }
             }
 
-            Log.d(TAG, mViolationMessage.toString());
+            Log.d(mLogTag, mViolationMessage.toString());
             mViolationMessage.setLength(0);
             tainted = true;
         }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4a62892..4bc7f39 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3455,6 +3455,10 @@
         if (!isShown()) {
             return;
         }
+
+        // Populate these here since they are related to the View that
+        // sends the event and should not be modified while dispatching
+        // to descendants.
         event.setClassName(getClass().getName());
         event.setPackageName(getContext().getPackageName());
         event.setEnabled(isEnabled());
@@ -3470,22 +3474,38 @@
 
         dispatchPopulateAccessibilityEvent(event);
 
-        AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event);
+        // In the beginning we called #isShown(), so we know that getParent() is not null.
+        getParent().requestSendAccessibilityEvent(this, event);
     }
 
     /**
-     * Dispatches an {@link AccessibilityEvent} to the {@link View} children
-     * to be populated.
+     * Dispatches an {@link AccessibilityEvent} to the {@link View} children to be populated.
+     * This method first calls {@link #onPopulateAccessibilityEvent(AccessibilityEvent)}
+     * on this view allowing it to populate information about itself and also decide
+     * whether to intercept the population i.e. to prevent its children from populating
+     * the event.
      *
      * @param event The event.
      *
      * @return True if the event population was completed.
      */
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        onPopulateAccessibilityEvent(event);
         return false;
     }
 
     /**
+     * Called from {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+     * giving a chance to this View to populate the accessibility evnet with
+     * information about itself.
+     *
+     * @param event The accessibility event which to populate.
+     */
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+
+    }
+
+    /**
      * Gets the {@link View} description. It briefly describes the view and is
      * primarily used for accessibility support. Set this property to enable
      * better accessibility support for your application. This is especially
@@ -5390,20 +5410,6 @@
      * to receive the hover event.
      */
     public boolean onHoverEvent(MotionEvent event) {
-        final int viewFlags = mViewFlags;
-
-        if (((viewFlags & CLICKABLE) != CLICKABLE &&
-                (viewFlags & LONG_CLICKABLE) != LONG_CLICKABLE)) {
-            // Nothing to do if the view is not clickable.
-            return false;
-        }
-
-        if ((viewFlags & ENABLED_MASK) == DISABLED) {
-            // A disabled view that is clickable still consumes the hover events, it just doesn't
-            // respond to them.
-            return true;
-        }
-
         switch (event.getAction()) {
             case MotionEvent.ACTION_HOVER_ENTER:
                 setHovered(true);
@@ -5414,7 +5420,7 @@
                 break;
         }
 
-        return true;
+        return false;
     }
 
     /**
@@ -5436,11 +5442,13 @@
             if ((mPrivateFlags & HOVERED) == 0) {
                 mPrivateFlags |= HOVERED;
                 refreshDrawableState();
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
             }
         } else {
             if ((mPrivateFlags & HOVERED) != 0) {
                 mPrivateFlags &= ~HOVERED;
                 refreshDrawableState();
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
             }
         }
     }
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 739758c..94eb429 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -19,7 +19,6 @@
 import android.app.AppGlobals;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.os.Bundle;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.SparseArray;
@@ -156,6 +155,13 @@
     private static final int MAXIMUM_FLING_VELOCITY = 8000;
 
     /**
+     * Distance between a touch up event denoting the end of a touch exploration
+     * gesture and the touch up event of a subsequent tap for the latter tap to be
+     * considered as a tap i.e. to perform a click.
+     */
+    private static final int TOUCH_EXPLORATION_TAP_SLOP = 80;
+
+    /**
      * The maximum size of View's drawing cache, expressed in bytes. This size
      * should be at least equal to the size of the screen in ARGB888 format.
      */
@@ -185,6 +191,7 @@
     private final int mTouchSlop;
     private final int mPagingTouchSlop;
     private final int mDoubleTapSlop;
+    private final int mScaledTouchExplorationTapSlop;
     private final int mWindowTouchSlop;
     private final int mMaximumDrawingCacheSize;
     private final int mOverscrollDistance;
@@ -206,6 +213,7 @@
         mTouchSlop = TOUCH_SLOP;
         mPagingTouchSlop = PAGING_TOUCH_SLOP;
         mDoubleTapSlop = DOUBLE_TAP_SLOP;
+        mScaledTouchExplorationTapSlop = TOUCH_EXPLORATION_TAP_SLOP;
         mWindowTouchSlop = WINDOW_TOUCH_SLOP;
         //noinspection deprecation
         mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
@@ -242,6 +250,7 @@
         mTouchSlop = (int) (sizeAndDensity * TOUCH_SLOP + 0.5f);
         mPagingTouchSlop = (int) (sizeAndDensity * PAGING_TOUCH_SLOP + 0.5f);
         mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
+        mScaledTouchExplorationTapSlop = (int) (density * TOUCH_EXPLORATION_TAP_SLOP + 0.5f);
         mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
 
         // Size of the screen in bytes, in ARGB_8888 format
@@ -444,6 +453,17 @@
     }
 
     /**
+     * @return Distance between a touch up event denoting the end of a touch exploration
+     * gesture and the touch up event of a subsequent tap for the latter tap to be
+     * considered as a tap i.e. to perform a click.
+     *
+     * @hide
+     */
+    public int getScaledTouchExplorationTapSlop() {
+        return mScaledTouchExplorationTapSlop;
+    }
+
+    /**
      * @return Distance a touch must be outside the bounds of a window for it
      * to be counted as outside the window for purposes of dismissing that
      * window.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 08daa28..7b404b4 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -586,6 +586,35 @@
     /**
      * {@inheritDoc}
      */
+    public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        ViewParent parent = getParent();
+        if (parent == null) {
+            return false;
+        }
+        final boolean propagate = onRequestSendAccessibilityEvent(child, event);
+        if (!propagate) {
+            return false;
+        }
+        return parent.requestSendAccessibilityEvent(this, event);
+    }
+
+    /**
+     * Called when a child has requested sending an {@link AccessibilityEvent} and
+     * gives an opportunity to its parent to augment the event.
+     *
+     * @param child The child which requests sending the event.
+     * @param event The event to be sent.
+     * @return True if the event should be sent.
+     *
+     * @see #requestSendAccessibilityEvent(View, AccessibilityEvent)
+     */
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public boolean dispatchUnhandledMove(View focused, int direction) {
         return mFocused != null &&
@@ -1216,9 +1245,8 @@
                 eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
                 handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, mHoveredChild);
                 eventNoHistory.setAction(action);
-
                 mHoveredChild = null;
-            } else if (action == MotionEvent.ACTION_HOVER_MOVE) {
+            } else {
                 // Pointer is still within the child.
                 handled |= dispatchTransformedGenericPointerEvent(event, mHoveredChild);
             }
@@ -1278,6 +1306,17 @@
         return handled;
     }
 
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        // Handle the event only if leaf. This guarantees that
+        // the leafs (or any custom class that returns true from
+        // this method) will get a change to process the hover.
+        if (getChildCount() == 0) {
+            return super.onHoverEvent(event);
+        }
+        return false;
+    }
+
     private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) {
         if (event.getHistorySize() == 0) {
             return event;
@@ -2091,11 +2130,16 @@
 
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        boolean populated = false;
+        // We first get a chance to populate the event.
+        onPopulateAccessibilityEvent(event);
+        // Let our children have a shot in populating the event.
         for (int i = 0, count = getChildCount(); i < count; i++) {
-            populated |= getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+            boolean handled = getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+            if (handled) {
+                return handled;
+            }
         }
-        return populated;
+        return false;
     }
 
     /**
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index d7d4c3f..655df39 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.graphics.Rect;
+import android.view.accessibility.AccessibilityEvent;
 
 /**
  * Defines the responsibilities for a class that will be a parent of a View.
@@ -222,4 +223,22 @@
      */
     public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
             boolean immediate);
+
+    /**
+     * Called by a child to request from its parent to send an {@link AccessibilityEvent}.
+     * The child has already populated a record for itself in the event and is delegating
+     * to its parent to send the event. The parent can optionally add a record for itself.
+     * <p>
+     * Note: An accessibility event is fired by an individual view which populates the
+     *       event with a record for its state and requests from its parent to perform
+     *       the sending. The parent can optionally add a record for itself before
+     *       dispatching the request to its parent. A parent can also choose not to
+     *       respect the request for sending the event. The accessibility event is sent
+     *       by the topmost view in the view tree.
+     *
+     * @param child The child which requests sending the event.
+     * @param event The event to be sent.
+     * @return True if the event was sent.
+     */
+    public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event);
 }
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 4104b07..f02daba 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -3530,6 +3530,14 @@
     public void childDrawableStateChanged(View child) {
     }
 
+    public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (mView == null) {
+            return false;
+        }
+        AccessibilityManager.getInstance(child.mContext).sendAccessibilityEvent(event);
+        return true;
+    }
+
     void checkThread() {
         if (mThread != Thread.currentThread()) {
             throw new CalledFromWrongThreadException(
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 9af19b8..11c9392 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -21,13 +21,26 @@
 import android.text.TextUtils;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * This class represents accessibility events that are sent by the system when
  * something notable happens in the user interface. For example, when a
  * {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc.
  * <p>
+ * An accessibility event is fired by an individual view which populates the event with
+ * a record for its state and requests from its parent to send the event to interested
+ * parties. The parent can optionally add a record for itself before dispatching a similar
+ * request to its parent. A parent can also choose not to respect the request for sending
+ * an event. The accessibility event is sent by the topmost view in the view tree.
+ * Therefore, an {@link android.accessibilityservice.AccessibilityService} can explore
+ * all records in an accessibility event to obtain more information about the context
+ * in which the event was fired.
+ * <p>
+ * A client can add, remove, and modify records. The getters and setters for individual
+ * properties operate on the current record which can be explicitly set by the client. By
+ * default current is the first record. Thus, querying a record would require setting
+ * it as the current one and interacting with the property getters and setters.
+ * <p>
  * This class represents various semantically different accessibility event
  * types. Each event type has associated a set of related properties. In other
  * words, each event type is characterized via a subset of the properties exposed
@@ -145,7 +158,7 @@
  * @see android.view.accessibility.AccessibilityManager
  * @see android.accessibilityservice.AccessibilityService
  */
-public final class AccessibilityEvent implements Parcelable {
+public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable {
 
     /**
      * Invalid selection/focus position.
@@ -207,6 +220,26 @@
     public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;
 
     /**
+     * Represents the event of a hover enter over a {@link android.view.View}.
+     */
+    public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;
+
+    /**
+     * Represents the event of a hover exit over a {@link android.view.View}.
+     */
+    public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;
+
+    /**
+     * Represents the event of starting a touch exploration gesture.
+     */
+    public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;
+
+    /**
+     * Represents the event of ending a touch exploration gesture.
+     */
+    public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
+
+    /**
      * Mask for {@link AccessibilityEvent} all types.
      *
      * @see #TYPE_VIEW_CLICKED
@@ -219,116 +252,53 @@
      */
     public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
 
-    private static final int MAX_POOL_SIZE = 2;
+    private static final int MAX_POOL_SIZE = 10;
     private static final Object mPoolLock = new Object();
     private static AccessibilityEvent sPool;
     private static int sPoolSize;
 
-    private static final int CHECKED = 0x00000001;
-    private static final int ENABLED = 0x00000002;
-    private static final int PASSWORD = 0x00000004;
-    private static final int FULL_SCREEN = 0x00000080;
-
     private AccessibilityEvent mNext;
+    private boolean mIsInPool;
 
     private int mEventType;
-    private int mBooleanProperties;
-    private int mCurrentItemIndex;
-    private int mItemCount;
-    private int mFromIndex;
-    private int mAddedCount;
-    private int mRemovedCount;
-
+    private CharSequence mPackageName;
     private long mEventTime;
 
-    private CharSequence mClassName;
-    private CharSequence mPackageName;
-    private CharSequence mContentDescription;
-    private CharSequence mBeforeText;
-
-    private Parcelable mParcelableData;
-
-    private final List<CharSequence> mText = new ArrayList<CharSequence>();
-
-    private boolean mIsInPool;
+    private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>();
 
     /*
      * Hide constructor from clients.
      */
     private AccessibilityEvent() {
-        mCurrentItemIndex = INVALID_POSITION;
+
     }
 
     /**
-     * Gets if the source is checked.
+     * Gets the number of records contained in the event.
      *
-     * @return True if the view is checked, false otherwise.
+     * @return The number of records.
      */
-    public boolean isChecked() {
-        return getBooleanProperty(CHECKED);
+    public int getRecordCount() {
+        return mRecords.size();
     }
 
     /**
-     * Sets if the source is checked.
+     * Appends an {@link AccessibilityRecord} to the end of event records.
      *
-     * @param isChecked True if the view is checked, false otherwise.
+     * @param record The record to append.
      */
-    public void setChecked(boolean isChecked) {
-        setBooleanProperty(CHECKED, isChecked);
+    public void appendRecord(AccessibilityRecord record) {
+        mRecords.add(record);
     }
 
     /**
-     * Gets if the source is enabled.
+     * Gets the records at a given index.
      *
-     * @return True if the view is enabled, false otherwise.
+     * @param index The index.
+     * @return The records at the specified index.
      */
-    public boolean isEnabled() {
-        return getBooleanProperty(ENABLED);
-    }
-
-    /**
-     * Sets if the source is enabled.
-     *
-     * @param isEnabled True if the view is enabled, false otherwise.
-     */
-    public void setEnabled(boolean isEnabled) {
-        setBooleanProperty(ENABLED, isEnabled);
-    }
-
-    /**
-     * Gets if the source is a password field.
-     *
-     * @return True if the view is a password field, false otherwise.
-     */
-    public boolean isPassword() {
-        return getBooleanProperty(PASSWORD);
-    }
-
-    /**
-     * Sets if the source is a password field.
-     *
-     * @param isPassword True if the view is a password field, false otherwise.
-     */
-    public void setPassword(boolean isPassword) {
-        setBooleanProperty(PASSWORD, isPassword);
-    }
-
-    /**
-     * Sets if the source is taking the entire screen.
-     *
-     * @param isFullScreen True if the source is full screen, false otherwise.
-     */
-    public void setFullScreen(boolean isFullScreen) {
-        setBooleanProperty(FULL_SCREEN, isFullScreen);
-    }
-
-    /**
-     * Gets if the source is taking the entire screen.
-     *
-     * @return True if the source is full screen, false otherwise.
-     */
-    public boolean isFullScreen() {
-        return getBooleanProperty(FULL_SCREEN);
+    public AccessibilityRecord getRecord(int index) {
+        return mRecords.get(index);
     }
 
     /**
@@ -350,96 +320,6 @@
     }
 
     /**
-     * Gets the number of items that can be visited.
-     *
-     * @return The number of items.
-     */
-    public int getItemCount() {
-        return mItemCount;
-    }
-
-    /**
-     * Sets the number of items that can be visited.
-     *
-     * @param itemCount The number of items.
-     */
-    public void setItemCount(int itemCount) {
-        mItemCount = itemCount;
-    }
-
-    /**
-     * Gets the index of the source in the list of items the can be visited.
-     *
-     * @return The current item index.
-     */
-    public int getCurrentItemIndex() {
-        return mCurrentItemIndex;
-    }
-
-    /**
-     * Sets the index of the source in the list of items that can be visited.
-     *
-     * @param currentItemIndex The current item index.
-     */
-    public void setCurrentItemIndex(int currentItemIndex) {
-        mCurrentItemIndex = currentItemIndex;
-    }
-
-    /**
-     * Gets the index of the first character of the changed sequence.
-     *
-     * @return The index of the first character.
-     */
-    public int getFromIndex() {
-        return mFromIndex;
-    }
-
-    /**
-     * Sets the index of the first character of the changed sequence.
-     *
-     * @param fromIndex The index of the first character.
-     */
-    public void setFromIndex(int fromIndex) {
-        mFromIndex = fromIndex;
-    }
-
-    /**
-     * Gets the number of added characters.
-     *
-     * @return The number of added characters.
-     */
-    public int getAddedCount() {
-        return mAddedCount;
-    }
-
-    /**
-     * Sets the number of added characters.
-     *
-     * @param addedCount The number of added characters.
-     */
-    public void setAddedCount(int addedCount) {
-        mAddedCount = addedCount;
-    }
-
-    /**
-     * Gets the number of removed characters.
-     *
-     * @return The number of removed characters.
-     */
-    public int getRemovedCount() {
-        return mRemovedCount;
-    }
-
-    /**
-     * Sets the number of removed characters.
-     *
-     * @param removedCount The number of removed characters.
-     */
-    public void setRemovedCount(int removedCount) {
-        mRemovedCount = removedCount;
-    }
-
-    /**
      * Gets the time in which this event was sent.
      *
      * @return The event time.
@@ -458,24 +338,6 @@
     }
 
     /**
-     * Gets the class name of the source.
-     *
-     * @return The class name.
-     */
-    public CharSequence getClassName() {
-        return mClassName;
-    }
-
-    /**
-     * Sets the class name of the source.
-     *
-     * @param className The lass name.
-     */
-    public void setClassName(CharSequence className) {
-        mClassName = className;
-    }
-
-    /**
      * Gets the package name of the source.
      *
      * @return The package name.
@@ -494,70 +356,6 @@
     }
 
     /**
-     * Gets the text of the event. The index in the list represents the priority
-     * of the text. Specifically, the lower the index the higher the priority.
-     *
-     * @return The text.
-     */
-    public List<CharSequence> getText() {
-        return mText;
-    }
-
-    /**
-     * Sets the text before a change.
-     *
-     * @return The text before the change.
-     */
-    public CharSequence getBeforeText() {
-        return mBeforeText;
-    }
-
-    /**
-     * Sets the text before a change.
-     *
-     * @param beforeText The text before the change.
-     */
-    public void setBeforeText(CharSequence beforeText) {
-        mBeforeText = beforeText;
-    }
-
-    /**
-     * Gets the description of the source.
-     *
-     * @return The description.
-     */
-    public CharSequence getContentDescription() {
-        return mContentDescription;
-    }
-
-    /**
-     * Sets the description of the source.
-     *
-     * @param contentDescription The description.
-     */
-    public void setContentDescription(CharSequence contentDescription) {
-        mContentDescription = contentDescription;
-    }
-
-    /**
-     * Gets the {@link Parcelable} data.
-     *
-     * @return The parcelable data.
-     */
-    public Parcelable getParcelableData() {
-        return mParcelableData;
-    }
-
-    /**
-     * Sets the {@link Parcelable} data of the event.
-     *
-     * @param parcelableData The parcelable data.
-     */
-    public void setParcelableData(Parcelable parcelableData) {
-        mParcelableData = parcelableData;
-    }
-
-    /**
      * Returns a cached instance if such is available or a new one is
      * instantiated with type property set.
      *
@@ -595,11 +393,11 @@
      * <p>
      * <b>Note: You must not touch the object after calling this function.</b>
      */
+    @Override
     public void recycle() {
         if (mIsInPool) {
             return;
         }
-
         clear();
         synchronized (mPoolLock) {
             if (sPoolSize <= MAX_POOL_SIZE) {
@@ -614,44 +412,15 @@
     /**
      * Clears the state of this instance.
      */
-    private void clear() {
+    @Override
+    protected void clear() {
+        super.clear();
         mEventType = 0;
-        mBooleanProperties = 0;
-        mCurrentItemIndex = INVALID_POSITION;
-        mItemCount = 0;
-        mFromIndex = 0;
-        mAddedCount = 0;
-        mRemovedCount = 0;
-        mEventTime = 0;
-        mClassName = null;
         mPackageName = null;
-        mContentDescription = null;
-        mBeforeText = null;
-        mParcelableData = null;
-        mText.clear();
-    }
-
-    /**
-     * Gets the value of a boolean property.
-     *
-     * @param property The property.
-     * @return The value.
-     */
-    private boolean getBooleanProperty(int property) {
-        return (mBooleanProperties & property) == property;
-    }
-
-    /**
-     * Sets a boolean property.
-     *
-     * @param property The property.
-     * @param value The value.
-     */
-    private void setBooleanProperty(int property, boolean value) {
-        if (value) {
-            mBooleanProperties |= property;
-        } else {
-            mBooleanProperties &= ~property;
+        mEventTime = 0;
+        while (!mRecords.isEmpty()) {
+            AccessibilityRecord record = mRecords.remove(0);
+            record.recycle();
         }
     }
 
@@ -662,38 +431,82 @@
      */
     public void initFromParcel(Parcel parcel) {
         mEventType = parcel.readInt();
-        mBooleanProperties = parcel.readInt();
-        mCurrentItemIndex = parcel.readInt();
-        mItemCount = parcel.readInt();
-        mFromIndex = parcel.readInt();
-        mAddedCount = parcel.readInt();
-        mRemovedCount = parcel.readInt();
-        mEventTime = parcel.readLong();
-        mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
-        mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
-        mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
-        mParcelableData = parcel.readParcelable(null);
-        parcel.readList(mText, null);
+        mEventTime = parcel.readLong();
+        readAccessibilityRecordFromParcel(this, parcel);
+
+        // Read the records.
+        final int recordCount = parcel.readInt();
+        for (int i = 0; i < recordCount; i++) {
+            AccessibilityRecord record = AccessibilityRecord.obtain();
+            readAccessibilityRecordFromParcel(record, parcel);
+            mRecords.add(record);
+        }
     }
 
+    /**
+     * Reads an {@link AccessibilityRecord} from a parcel.
+     *
+     * @param record The record to initialize.
+     * @param parcel The parcel to read from.
+     */
+    private void readAccessibilityRecordFromParcel(AccessibilityRecord record,
+            Parcel parcel) {
+        record.mBooleanProperties = parcel.readInt();
+        record.mCurrentItemIndex = parcel.readInt();
+        record.mItemCount = parcel.readInt();
+        record.mFromIndex = parcel.readInt();
+        record.mAddedCount = parcel.readInt();
+        record.mRemovedCount = parcel.readInt();
+        record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        record.mParcelableData = parcel.readParcelable(null);
+        parcel.readList(record.mText, null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(mEventType);
-        parcel.writeInt(mBooleanProperties);
-        parcel.writeInt(mCurrentItemIndex);
-        parcel.writeInt(mItemCount);
-        parcel.writeInt(mFromIndex);
-        parcel.writeInt(mAddedCount);
-        parcel.writeInt(mRemovedCount);
-        parcel.writeLong(mEventTime);
-        TextUtils.writeToParcel(mClassName, parcel, 0);
         TextUtils.writeToParcel(mPackageName, parcel, 0);
-        TextUtils.writeToParcel(mContentDescription, parcel, 0);
-        TextUtils.writeToParcel(mBeforeText, parcel, 0);
-        parcel.writeParcelable(mParcelableData, flags);
-        parcel.writeList(mText);
+        parcel.writeLong(mEventTime);
+        writeAccessibilityRecordToParcel(this, parcel, flags);
+
+        // Write the records.
+        final int recordCount = getRecordCount();
+        parcel.writeInt(recordCount);
+        for (int i = 0; i < recordCount; i++) {
+            AccessibilityRecord record = mRecords.get(i);
+            writeAccessibilityRecordToParcel(record, parcel, flags);
+        }
     }
 
+    /**
+     * Writes an {@link AccessibilityRecord} to a parcel.
+     *
+     * @param record The record to write.
+     * @param parcel The parcel to which to write.
+     */
+    private void writeAccessibilityRecordToParcel(AccessibilityRecord record, Parcel parcel,
+            int flags) {
+        parcel.writeInt(record.mBooleanProperties);
+        parcel.writeInt(record.mCurrentItemIndex);
+        parcel.writeInt(record.mItemCount);
+        parcel.writeInt(record.mFromIndex);
+        parcel.writeInt(record.mAddedCount);
+        parcel.writeInt(record.mRemovedCount);
+        TextUtils.writeToParcel(record.mClassName, parcel, flags);
+        TextUtils.writeToParcel(record.mContentDescription, parcel, flags);
+        TextUtils.writeToParcel(record.mBeforeText, parcel, flags);
+        parcel.writeParcelable(record.mParcelableData, flags);
+        parcel.writeList(record.mText);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public int describeContents() {
         return 0;
     }
@@ -701,24 +514,21 @@
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
-        builder.append(super.toString());
         builder.append("; EventType: " + mEventType);
         builder.append("; EventTime: " + mEventTime);
-        builder.append("; ClassName: " + mClassName);
         builder.append("; PackageName: " + mPackageName);
-        builder.append("; Text: " + mText);
-        builder.append("; ContentDescription: " + mContentDescription);
-        builder.append("; ItemCount: " + mItemCount);
-        builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
-        builder.append("; IsEnabled: " + isEnabled());
-        builder.append("; IsPassword: " + isPassword());
-        builder.append("; IsChecked: " + isChecked());
-        builder.append("; IsFullScreen: " + isFullScreen());
-        builder.append("; BeforeText: " + mBeforeText);
-        builder.append("; FromIndex: " + mFromIndex);
-        builder.append("; AddedCount: " + mAddedCount);
-        builder.append("; RemovedCount: " + mRemovedCount);
-        builder.append("; ParcelableData: " + mParcelableData);
+        builder.append(" \n{\n");
+        builder.append(super.toString());
+        builder.append("\n");
+        for (int i = 0; i < mRecords.size(); i++) {
+            AccessibilityRecord record = mRecords.get(i);
+            builder.append("  Record ");
+            builder.append(i);
+            builder.append(":");
+            builder.append(record.toString());
+            builder.append("\n");
+        }
+        builder.append("}\n");
         return builder.toString();
     }
 
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 22cb0d4..dd77193 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -16,6 +16,8 @@
 
 package android.view.accessibility;
 
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.Context;
 import android.content.pm.ServiceInfo;
 import android.os.Binder;
@@ -44,6 +46,8 @@
  * @see android.content.Context#getSystemService
  */
 public final class AccessibilityManager {
+    private static final boolean DEBUG = false;
+
     private static final String LOG_TAG = "AccessibilityManager";
 
     static final Object sInstanceSync = new Object();
@@ -164,7 +168,7 @@
             long identityToken = Binder.clearCallingIdentity();
             doRecycle = mService.sendAccessibilityEvent(event);
             Binder.restoreCallingIdentity(identityToken);
-            if (false) {
+            if (DEBUG) {
                 Log.i(LOG_TAG, event + " sent");
             }
         } catch (RemoteException re) {
@@ -185,7 +189,7 @@
         }
         try {
             mService.interrupt();
-            if (false) {
+            if (DEBUG) {
                 Log.i(LOG_TAG, "Requested interrupt from all services");
             }
         } catch (RemoteException re) {
@@ -202,7 +206,33 @@
         List<ServiceInfo> services = null;
         try {
             services = mService.getAccessibilityServiceList();
-            if (false) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+        }
+        return Collections.unmodifiableList(services);
+    }
+
+    /**
+     * Returns the {@link ServiceInfo}s of the enabled accessibility services
+     * for a given feedback type.
+     *
+     * @param feedbackType The type of feedback.
+     * @return An unmodifiable list with {@link ServiceInfo}s.
+     *
+     * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
+     * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
+     * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
+     * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+     * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
+     */
+    public List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
+        List<ServiceInfo> services = null;
+        try {
+            services = mService.getEnabledAccessibilityServiceList(feedbackType);
+            if (DEBUG) {
                 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
             }
         } catch (RemoteException re) {
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
new file mode 100644
index 0000000..e095f43
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a record in an accessibility event. This class encapsulates
+ * the information for a {@link android.view.View}. Note that not all properties
+ * are applicable to all view types. For detailed information please refer to
+ * {@link AccessibilityEvent}.
+ *
+ * @see AccessibilityEvent
+ */
+public class AccessibilityRecord {
+
+    private static final int INVALID_POSITION = -1;
+
+    private static final int PROPERTY_CHECKED = 0x00000001;
+    private static final int PROPERTY_ENABLED = 0x00000002;
+    private static final int PROPERTY_PASSWORD = 0x00000004;
+    private static final int PROPERTY_FULL_SCREEN = 0x00000080;
+
+    private static final int MAX_POOL_SIZE = 10;
+    private static final Object mPoolLock = new Object();
+    private static AccessibilityRecord sPool;
+    private static int sPoolSize;
+
+    private AccessibilityRecord mNext;
+    private boolean mIsInPool;
+
+    protected int mBooleanProperties;
+    protected int mCurrentItemIndex;
+    protected int mItemCount;
+    protected int mFromIndex;
+    protected int mAddedCount;
+    protected int mRemovedCount;
+
+    protected CharSequence mClassName;
+    protected CharSequence mContentDescription;
+    protected CharSequence mBeforeText;
+    protected Parcelable mParcelableData;
+
+    protected final List<CharSequence> mText = new ArrayList<CharSequence>();
+
+    /*
+     * Hide constructor.
+     */
+    protected AccessibilityRecord() {
+
+    }
+
+    /**
+     * Gets if the source is checked.
+     *
+     * @return True if the view is checked, false otherwise.
+     */
+    public boolean isChecked() {
+        return getBooleanProperty(PROPERTY_CHECKED);
+    }
+
+    /**
+     * Sets if the source is checked.
+     *
+     * @param isChecked True if the view is checked, false otherwise.
+     */
+    public void setChecked(boolean isChecked) {
+        setBooleanProperty(PROPERTY_CHECKED, isChecked);
+    }
+
+    /**
+     * Gets if the source is enabled.
+     *
+     * @return True if the view is enabled, false otherwise.
+     */
+    public boolean isEnabled() {
+        return getBooleanProperty(PROPERTY_ENABLED);
+    }
+
+    /**
+     * Sets if the source is enabled.
+     *
+     * @param isEnabled True if the view is enabled, false otherwise.
+     */
+    public void setEnabled(boolean isEnabled) {
+        setBooleanProperty(PROPERTY_ENABLED, isEnabled);
+    }
+
+    /**
+     * Gets if the source is a password field.
+     *
+     * @return True if the view is a password field, false otherwise.
+     */
+    public boolean isPassword() {
+        return getBooleanProperty(PROPERTY_PASSWORD);
+    }
+
+    /**
+     * Sets if the source is a password field.
+     *
+     * @param isPassword True if the view is a password field, false otherwise.
+     */
+    public void setPassword(boolean isPassword) {
+        setBooleanProperty(PROPERTY_PASSWORD, isPassword);
+    }
+
+    /**
+     * Sets if the source is taking the entire screen.
+     *
+     * @param isFullScreen True if the source is full screen, false otherwise.
+     */
+    public void setFullScreen(boolean isFullScreen) {
+        setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
+    }
+
+    /**
+     * Gets if the source is taking the entire screen.
+     *
+     * @return True if the source is full screen, false otherwise.
+     */
+    public boolean isFullScreen() {
+        return getBooleanProperty(PROPERTY_FULL_SCREEN);
+    }
+
+    /**
+     * Gets the number of items that can be visited.
+     *
+     * @return The number of items.
+     */
+    public int getItemCount() {
+        return mItemCount;
+    }
+
+    /**
+     * Sets the number of items that can be visited.
+     *
+     * @param itemCount The number of items.
+     */
+    public void setItemCount(int itemCount) {
+        mItemCount = itemCount;
+    }
+
+    /**
+     * Gets the index of the source in the list of items the can be visited.
+     *
+     * @return The current item index.
+     */
+    public int getCurrentItemIndex() {
+        return mCurrentItemIndex;
+    }
+
+    /**
+     * Sets the index of the source in the list of items that can be visited.
+     *
+     * @param currentItemIndex The current item index.
+     */
+    public void setCurrentItemIndex(int currentItemIndex) {
+        mCurrentItemIndex = currentItemIndex;
+    }
+
+    /**
+     * Gets the index of the first character of the changed sequence.
+     *
+     * @return The index of the first character.
+     */
+    public int getFromIndex() {
+        return mFromIndex;
+    }
+
+    /**
+     * Sets the index of the first character of the changed sequence.
+     *
+     * @param fromIndex The index of the first character.
+     */
+    public void setFromIndex(int fromIndex) {
+        mFromIndex = fromIndex;
+    }
+
+    /**
+     * Gets the number of added characters.
+     *
+     * @return The number of added characters.
+     */
+    public int getAddedCount() {
+        return mAddedCount;
+    }
+
+    /**
+     * Sets the number of added characters.
+     *
+     * @param addedCount The number of added characters.
+     */
+    public void setAddedCount(int addedCount) {
+        mAddedCount = addedCount;
+    }
+
+    /**
+     * Gets the number of removed characters.
+     *
+     * @return The number of removed characters.
+     */
+    public int getRemovedCount() {
+        return mRemovedCount;
+    }
+
+    /**
+     * Sets the number of removed characters.
+     *
+     * @param removedCount The number of removed characters.
+     */
+    public void setRemovedCount(int removedCount) {
+        mRemovedCount = removedCount;
+    }
+
+    /**
+     * Gets the class name of the source.
+     *
+     * @return The class name.
+     */
+    public CharSequence getClassName() {
+        return mClassName;
+    }
+
+    /**
+     * Sets the class name of the source.
+     *
+     * @param className The lass name.
+     */
+    public void setClassName(CharSequence className) {
+        mClassName = className;
+    }
+
+    /**
+     * Gets the text of the event. The index in the list represents the priority
+     * of the text. Specifically, the lower the index the higher the priority.
+     *
+     * @return The text.
+     */
+    public List<CharSequence> getText() {
+        return mText;
+    }
+
+    /**
+     * Sets the text before a change.
+     *
+     * @return The text before the change.
+     */
+    public CharSequence getBeforeText() {
+        return mBeforeText;
+    }
+
+    /**
+     * Sets the text before a change.
+     *
+     * @param beforeText The text before the change.
+     */
+    public void setBeforeText(CharSequence beforeText) {
+        mBeforeText = beforeText;
+    }
+
+    /**
+     * Gets the description of the source.
+     *
+     * @return The description.
+     */
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
+     * Sets the description of the source.
+     *
+     * @param contentDescription The description.
+     */
+    public void setContentDescription(CharSequence contentDescription) {
+        mContentDescription = contentDescription;
+    }
+
+    /**
+     * Gets the {@link Parcelable} data.
+     *
+     * @return The parcelable data.
+     */
+    public Parcelable getParcelableData() {
+        return mParcelableData;
+    }
+
+    /**
+     * Sets the {@link Parcelable} data of the event.
+     *
+     * @param parcelableData The parcelable data.
+     */
+    public void setParcelableData(Parcelable parcelableData) {
+        mParcelableData = parcelableData;
+    }
+
+    /**
+     * Gets the value of a boolean property.
+     *
+     * @param property The property.
+     * @return The value.
+     */
+    public boolean getBooleanProperty(int property) {
+        return (mBooleanProperties & property) == property;
+    }
+
+    /**
+     * Sets a boolean property.
+     *
+     * @param property The property.
+     * @param value The value.
+     */
+    private void setBooleanProperty(int property, boolean value) {
+        if (value) {
+            mBooleanProperties |= property;
+        } else {
+            mBooleanProperties &= ~property;
+        }
+    }
+
+    /**
+     * Returns a cached instance if such is available or a new one is
+     * instantiated.
+     *
+     * @return An instance.
+     */
+    protected static AccessibilityRecord obtain() {
+        synchronized (mPoolLock) {
+            if (sPool != null) {
+                AccessibilityRecord record = sPool;
+                sPool = sPool.mNext;
+                sPoolSize--;
+                record.mNext = null;
+                record.mIsInPool = false;
+                return record;
+            }
+            return new AccessibilityRecord();
+        }
+    }
+
+    /**
+     * Return an instance back to be reused.
+     * <p>
+     * <b>Note: You must not touch the object after calling this function.</b>
+     */
+    public void recycle() {
+        if (mIsInPool) {
+            return;
+        }
+        clear();
+        synchronized (mPoolLock) {
+            if (sPoolSize <= MAX_POOL_SIZE) {
+                mNext = sPool;
+                sPool = this;
+                mIsInPool = true;
+                sPoolSize++;
+            }
+        }
+    }
+
+    /**
+     * Clears the state of this instance.
+     */
+    protected void clear() {
+        mBooleanProperties = 0;
+        mCurrentItemIndex = INVALID_POSITION;
+        mItemCount = 0;
+        mFromIndex = 0;
+        mAddedCount = 0;
+        mRemovedCount = 0;
+        mClassName = null;
+        mContentDescription = null;
+        mBeforeText = null;
+        mParcelableData = null;
+        mText.clear();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(" [ ClassName: " + mClassName);
+        builder.append("; Text: " + mText);
+        builder.append("; ContentDescription: " + mContentDescription);
+        builder.append("; ItemCount: " + mItemCount);
+        builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
+        builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
+        builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
+        builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
+        builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
+        builder.append("; BeforeText: " + mBeforeText);
+        builder.append("; FromIndex: " + mFromIndex);
+        builder.append("; AddedCount: " + mAddedCount);
+        builder.append("; RemovedCount: " + mRemovedCount);
+        builder.append("; ParcelableData: " + mParcelableData);
+        builder.append(" ]");
+        return builder.toString();
+    }
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 7633569..aaaae32 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -35,5 +35,7 @@
 
     List<ServiceInfo> getAccessibilityServiceList();
 
+    List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType);
+
     void interrupt();
 }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6cb5c35..d63d421 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -55,6 +55,7 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -2556,6 +2557,17 @@
     }
 
     @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        // Add a record for ourselves as well.
+        AccessibilityEvent record = AccessibilityEvent.obtain();
+        // Set the class since it is not populated in #dispatchPopulateAccessibilityEvent
+        record.setClassName(getClass().getName());
+        child.dispatchPopulateAccessibilityEvent(record);
+        event.appendRecord(record);
+        return true;
+    }
+
+    @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         return false;
     }
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index f16efbd..060f1a9 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -876,7 +876,6 @@
 
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        boolean populated = false;
         // This is an exceptional case which occurs when a window gets the
         // focus and sends a focus event via its focused child to announce
         // current focus/selection. AdapterView fires selection but not focus
@@ -885,22 +884,27 @@
             event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
         }
 
-        // we send selection events only from AdapterView to avoid
-        // generation of such event for each child
+        // We first get a chance to populate the event.
+        onPopulateAccessibilityEvent(event);
+
+        // We send selection events only from AdapterView to avoid
+        // generation of such event for each child.
         View selectedView = getSelectedView();
         if (selectedView != null) {
-            populated = selectedView.dispatchPopulateAccessibilityEvent(event);
+            return selectedView.dispatchPopulateAccessibilityEvent(event);
         }
 
-        if (!populated) {
-            if (selectedView != null) {
-                event.setEnabled(selectedView.isEnabled());
-            }
-            event.setItemCount(getCount());
-            event.setCurrentItemIndex(getSelectedItemPosition());
-        }
+        return false;
+    }
 
-        return populated;
+    @Override
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        View selectedView = getSelectedView();
+        if (selectedView != null) {
+            event.setEnabled(selectedView.isEnabled());
+        }
+        event.setItemCount(getCount());
+        event.setCurrentItemIndex(getSelectedItemPosition());
     }
 
     @Override
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index bf63607..bd595a5 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -199,11 +199,8 @@
     }
 
     @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        boolean populated = super.dispatchPopulateAccessibilityEvent(event);
-        if (!populated) {
-            event.setChecked(mChecked);
-        }
-        return populated;
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+        event.setChecked(mChecked);
     }
 }
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 0df45cc..f050d41 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -208,22 +208,18 @@
     }
 
     @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
 
-        if (!populated) {
-            int resourceId = 0;
-            if (mChecked) {
-                resourceId = R.string.accessibility_compound_button_selected;
-            } else {
-                resourceId = R.string.accessibility_compound_button_unselected;
-            }
-            String state = getResources().getString(resourceId);
-            event.getText().add(state);
-            event.setChecked(mChecked);
+        int resourceId = 0;
+        if (mChecked) {
+            resourceId = R.string.accessibility_compound_button_selected;
+        } else {
+            resourceId = R.string.accessibility_compound_button_unselected;
         }
-
-        return populated;
+        String state = getResources().getString(resourceId);
+        event.getText().add(state);
+        event.setChecked(mChecked);
     }
 
     @Override
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 7210e21..30fb927 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -353,13 +353,14 @@
     }
 
     @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+
+        final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
                 | DateUtils.FORMAT_SHOW_YEAR;
         String selectedDateUtterance = DateUtils.formatDateTime(mContext,
                 mCurrentDate.getTimeInMillis(), flags);
         event.getText().add(selectedDateUtterance);
-        return true;
     }
 
     /**
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index d76a956..5618dbe 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1998,36 +1998,32 @@
     }
 
     @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
 
         // If the item count is less than 15 then subtract disabled items from the count and
         // position. Otherwise ignore disabled items.
-        if (!populated) {
-            int itemCount = 0;
-            int currentItemIndex = getSelectedItemPosition();
+        int itemCount = 0;
+        int currentItemIndex = getSelectedItemPosition();
 
-            ListAdapter adapter = getAdapter();
-            if (adapter != null) {
-                final int count = adapter.getCount();
-                if (count < 15) {
-                    for (int i = 0; i < count; i++) {
-                        if (adapter.isEnabled(i)) {
-                            itemCount++;
-                        } else if (i <= currentItemIndex) {
-                            currentItemIndex--;
-                        }
+        ListAdapter adapter = getAdapter();
+        if (adapter != null) {
+            final int count = adapter.getCount();
+            if (count < 15) {
+                for (int i = 0; i < count; i++) {
+                    if (adapter.isEnabled(i)) {
+                        itemCount++;
+                    } else if (i <= currentItemIndex) {
+                        currentItemIndex--;
                     }
-                } else {
-                    itemCount = count;
                 }
+            } else {
+                itemCount = count;
             }
-
-            event.setItemCount(itemCount);
-            event.setCurrentItemIndex(currentItemIndex);
         }
 
-        return populated;
+        event.setItemCount(itemCount);
+        event.setCurrentItemIndex(currentItemIndex);
     }
 
     /**
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 8db34d9..96d41a0 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -1027,12 +1027,10 @@
     }
 
     @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        if (!super.dispatchPopulateAccessibilityEvent(event)) {
-            event.setItemCount(mMax);
-            event.setCurrentItemIndex(mProgress);
-        }
-        return true;
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+        event.setItemCount(mMax);
+        event.setCurrentItemIndex(mProgress);
     }
 
     /**
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 6f76dd0..31ec785 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -427,12 +427,19 @@
 
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        onPopulateAccessibilityEvent(event);
+        // Dispatch only to the selected tab.
+        if (mSelectedTab != -1) {
+            return getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
+        }
+        return false;
+    }
+
+    @Override
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
         event.setItemCount(getTabCount());
         event.setCurrentItemIndex(mSelectedTab);
-        if (mSelectedTab != -1) {
-            getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
-        }
-        return true;
     }
 
     /**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ed98401..4d3aa68 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7896,9 +7896,9 @@
     }
 
     @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         if (!isShown()) {
-            return false;
+            return;
         }
 
         final boolean isPassword = hasPasswordTransformationMethod();
@@ -7914,7 +7914,6 @@
         } else {
             event.setPassword(isPassword);
         }
-        return false;
     }
 
     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 029d690..423e735c 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -409,7 +409,9 @@
     }
 
     @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+
         int flags = DateUtils.FORMAT_SHOW_TIME;
         if (mIs24HourView) {
             flags |= DateUtils.FORMAT_24HOUR;
@@ -421,7 +423,6 @@
         String selectedDateUtterance = DateUtils.formatDateTime(mContext,
                 mTempCalendar.getTimeInMillis(), flags);
         event.getText().add(selectedDateUtterance);
-        return true;
     }
 
     private void updateHourControl() {