content: /*
   * Copyright (C) 2006 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 com.actionbarsherlock.internal.widget;
  
  import android.content.Context;
  import android.database.DataSetObserver;
  import android.os.Parcelable;
  import android.os.SystemClock;
  import android.util.AttributeSet;
  import android.util.SparseArray;
  import android.view.ContextMenu;
  import android.view.SoundEffectConstants;
  import android.view.View;
  import android.view.ViewDebug;
  import android.view.ViewGroup;
  import android.view.accessibility.AccessibilityEvent;
  import android.view.accessibility.AccessibilityNodeInfo;
  import android.widget.Adapter;
  import android.widget.AdapterView.OnItemClickListener;
  import android.widget.ListView;
  
  
  /**
   * An AdapterView is a view whose children are determined by an {@link Adapter}.
   *
   * <p>
   * See {@link ListView}, {@link GridView}, {@link Spinner} and
   *      {@link Gallery} for commonly used subclasses of AdapterView.
   *
   * <div class="special reference">
   * <h3>Developer Guides</h3>
   * <p>For more information about using AdapterView, read the
   * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
   * developer guide.</p></div>
   */
  public abstract class IcsAdapterView<T extends Adapter> extends ViewGroup {
  
      /**
       * The item view type returned by {@link Adapter#getItemViewType(int)} when
       * the adapter does not want the item's view recycled.
       */
      public static final int ITEM_VIEW_TYPE_IGNORE = -1;
  
      /**
       * The item view type returned by {@link Adapter#getItemViewType(int)} when
       * the item is a header or footer.
       */
      public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
  
      /**
       * The position of the first child displayed
       */
      @ViewDebug.ExportedProperty(category = "scrolling")
      int mFirstPosition = 0;
  
      /**
       * The offset in pixels from the top of the AdapterView to the top
       * of the view to select during the next layout.
       */
      int mSpecificTop;
  
      /**
       * Position from which to start looking for mSyncRowId
       */
      int mSyncPosition;
  
      /**
       * Row id to look for when data has changed
       */
      long mSyncRowId = INVALID_ROW_ID;
  
      /**
       * Height of the view when mSyncPosition and mSyncRowId where set
       */
      long mSyncHeight;
  
      /**
       * True if we need to sync to mSyncRowId
       */
      boolean mNeedSync = false;
  
      /**
       * Indicates whether to sync based on the selection or position. Possible
       * values are {@link #SYNC_SELECTED_POSITION} or
       * {@link #SYNC_FIRST_POSITION}.
       */
      int mSyncMode;
  
      /**
       * Our height after the last layout
       */
      private int mLayoutHeight;
  
      /**
       * Sync based on the selected child
       */
      static final int SYNC_SELECTED_POSITION = 0;
  
      /**
       * Sync based on the first child displayed
       */
      static final int SYNC_FIRST_POSITION = 1;
  
      /**
       * Maximum amount of time to spend in {@link #findSyncPosition()}
       */
      static final int SYNC_MAX_DURATION_MILLIS = 100;
  
      /**
       * Indicates that this view is currently being laid out.
       */
      boolean mInLayout = false;
  
      /**
       * The listener that receives notifications when an item is selected.
       */
      OnItemSelectedListener mOnItemSelectedListener;
  
      /**
       * The listener that receives notifications when an item is clicked.
       */
      OnItemClickListener mOnItemClickListener;
  
      /**
       * The listener that receives notifications when an item is long clicked.
       */
      OnItemLongClickListener mOnItemLongClickListener;
  
      /**
       * True if the data has changed since the last layout
       */
      boolean mDataChanged;
  
      /**
       * The position within the adapter's data set of the item to select
       * during the next layout.
       */
      @ViewDebug.ExportedProperty(category = "list")
      int mNextSelectedPosition = INVALID_POSITION;
  
      /**
       * The item id of the item to select during the next layout.
       */
      long mNextSelectedRowId = INVALID_ROW_ID;
  
      /**
       * The position within the adapter's data set of the currently selected item.
       */
      @ViewDebug.ExportedProperty(category = "list")
      int mSelectedPosition = INVALID_POSITION;
  
      /**
       * The item id of the currently selected item.
       */
      long mSelectedRowId = INVALID_ROW_ID;
  
      /**
       * View to show if there are no items to show.
       */
      private View mEmptyView;
  
      /**
       * The number of items in the current adapter.
       */
      @ViewDebug.ExportedProperty(category = "list")
      int mItemCount;
  
      /**
       * The number of items in the adapter before a data changed event occurred.
       */
      int mOldItemCount;
  
      /**
       * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
       * number of items in the current adapter.
       */
      public static final int INVALID_POSITION = -1;
  
      /**
       * Represents an empty or invalid row id
       */
      public static final long INVALID_ROW_ID = Long.MIN_VALUE;
  
      /**
       * The last selected position we used when notifying
       */
      int mOldSelectedPosition = INVALID_POSITION;
  
      /**
       * The id of the last selected position we used when notifying
       */
      long mOldSelectedRowId = INVALID_ROW_ID;
  
      /**
       * Indicates what focusable state is requested when calling setFocusable().
       * In addition to this, this view has other criteria for actually
       * determining the focusable state (such as whether its empty or the text
       * filter is shown).
       *
       * @see #setFocusable(boolean)
       * @see #checkFocus()
       */
      private boolean mDesiredFocusableState;
      private boolean mDesiredFocusableInTouchModeState;
  
      private SelectionNotifier mSelectionNotifier;
      /**
       * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
       * This is used to layout the children during a layout pass.
       */
      boolean mBlockLayoutRequests = false;
  
      public IcsAdapterView(Context context) {
          super(context);
      }
  
      public IcsAdapterView(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
  
      public IcsAdapterView(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
      }
  
      /**
       * Register a callback to be invoked when an item in this AdapterView has
       * been clicked.
       *
       * @param listener The callback that will be invoked.
       */
      public void setOnItemClickListener(OnItemClickListener listener) {
          mOnItemClickListener = listener;
      }
  
      /**
       * @return The callback to be invoked with an item in this AdapterView has
       *         been clicked, or null id no callback has been set.
       */
      public final OnItemClickListener getOnItemClickListener() {
          return mOnItemClickListener;
      }
  
      /**
       * Call the OnItemClickListener, if it is defined.
       *
       * @param view The view within the AdapterView that was clicked.
       * @param position The position of the view in the adapter.
       * @param id The row id of the item that was clicked.
       * @return True if there was an assigned OnItemClickListener that was
       *         called, false otherwise is returned.
       */
      public boolean performItemClick(View view, int position, long id) {
          if (mOnItemClickListener != null) {
              playSoundEffect(SoundEffectConstants.CLICK);
              if (view != null) {
                  view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
              }
              mOnItemClickListener.onItemClick(/*this*/null, view, position, id);
              return true;
          }
  
          return false;
      }
  
      /**
       * Interface definition for a callback to be invoked when an item in this
       * view has been clicked and held.
       */
      public interface OnItemLongClickListener {
          /**
           * Callback method to be invoked when an item in this view has been
           * clicked and held.
           *
           * Implementers can call getItemAtPosition(position) if they need to access
           * the data associated with the selected item.
           *
           * @param parent The AbsListView where the click happened
           * @param view The view within the AbsListView that was clicked
           * @param position The position of the view in the list
           * @param id The row id of the item that was clicked
           *
           * @return true if the callback consumed the long click, false otherwise
           */
          boolean onItemLongClick(IcsAdapterView<?> parent, View view, int position, long id);
      }
  
  
      /**
       * Register a callback to be invoked when an item in this AdapterView has
       * been clicked and held
       *
       * @param listener The callback that will run
       */
      public void setOnItemLongClickListener(OnItemLongClickListener listener) {
          if (!isLongClickable()) {
              setLongClickable(true);
          }
          mOnItemLongClickListener = listener;
      }
  
      /**
       * @return The callback to be invoked with an item in this AdapterView has
       *         been clicked and held, or null id no callback as been set.
       */
      public final OnItemLongClickListener getOnItemLongClickListener() {
          return mOnItemLongClickListener;
      }
  
      /**
       * Interface definition for a callback to be invoked when
       * an item in this view has been selected.
       */
      public interface OnItemSelectedListener {
          /**
           * <p>Callback method to be invoked when an item in this view has been
           * selected. This callback is invoked only when the newly selected
           * position is different from the previously selected position or if
           * there was no selected item.</p>
           *
           * Impelmenters can call getItemAtPosition(position) if they need to access the
           * data associated with the selected item.
           *
           * @param parent The AdapterView where the selection happened
           * @param view The view within the AdapterView that was clicked
           * @param position The position of the view in the adapter
           * @param id The row id of the item that is selected
           */
          void onItemSelected(IcsAdapterView<?> parent, View view, int position, long id);
  
          /**
           * Callback method to be invoked when the selection disappears from this
           * view. The selection can disappear for instance when touch is activated
           * or when the adapter becomes empty.
           *
           * @param parent The AdapterView that now contains no selected item.
           */
          void onNothingSelected(IcsAdapterView<?> parent);
      }
  
  
      /**
       * Register a callback to be invoked when an item in this AdapterView has
       * been selected.
       *
       * @param listener The callback that will run
       */
      public void setOnItemSelectedListener(OnItemSelectedListener listener) {
          mOnItemSelectedListener = listener;
      }
  
      public final OnItemSelectedListener getOnItemSelectedListener() {
          return mOnItemSelectedListener;
      }
  
      /**
       * Extra menu information provided to the
       * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
       * callback when a context menu is brought up for this AdapterView.
       *
       */
      public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
  
          public AdapterContextMenuInfo(View targetView, int position, long id) {
              this.targetView = targetView;
              this.position = position;
              this.id = id;
          }
  
          /**
           * The child view for which the context menu is being displayed. This
           * will be one of the children of this AdapterView.
           */
          public View targetView;
  
          /**
           * The position in the adapter for which the context menu is being
           * displayed.
           */
          public int position;
  
          /**
           * The row id of the item for which the context menu is being displayed.
           */
          public long id;
      }
  
      /**
       * Returns the adapter currently associated with this widget.
       *
       * @return The adapter used to provide this view's content.
       */
      public abstract T getAdapter();
  
      /**
       * Sets the adapter that provides the data and the views to represent the data
       * in this widget.
       *
       * @param adapter The adapter to use to create this view's content.
       */
      public abstract void setAdapter(T adapter);
  
      /**
       * This method is not supported and throws an UnsupportedOperationException when called.
       *
       * @param child Ignored.
       *
       * @throws UnsupportedOperationException Every time this method is invoked.
       */
      @Override
      public void addView(View child) {
          throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
      }
  
      /**
       * This method is not supported and throws an UnsupportedOperationException when called.
       *
       * @param child Ignored.
       * @param index Ignored.
       *
       * @throws UnsupportedOperationException Every time this method is invoked.
       */
      @Override
      public void addView(View child, int index) {
          throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
      }
  
      /**
       * This method is not supported and throws an UnsupportedOperationException when called.
       *
       * @param child Ignored.
       * @param params Ignored.
       *
       * @throws UnsupportedOperationException Every time this method is invoked.
       */
      @Override
      public void addView(View child, LayoutParams params) {
          throw new UnsupportedOperationException("addView(View, LayoutParams) "
                  + "is not supported in AdapterView");
      }
  
      /**
       * This method is not supported and throws an UnsupportedOperationException when called.
       *
       * @param child Ignored.
       * @param index Ignored.
       * @param params Ignored.
       *
       * @throws UnsupportedOperationException Every time this method is invoked.
       */
      @Override
      public void addView(View child, int index, LayoutParams params) {
          throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
                  + "is not supported in AdapterView");
      }
  
      /**
       * This method is not supported and throws an UnsupportedOperationException when called.
       *
       * @param child Ignored.
       *
       * @throws UnsupportedOperationException Every time this method is invoked.
       */
      @Override
      public void removeView(View child) {
          throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
      }
  
      /**
       * This method is not supported and throws an UnsupportedOperationException when called.
       *
       * @param index Ignored.
       *
       * @throws UnsupportedOperationException Every time this method is invoked.
       */
      @Override
      public void removeViewAt(int index) {
          throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
      }
  
      /**
       * This method is not supported and throws an UnsupportedOperationException when called.
       *
       * @throws UnsupportedOperationException Every time this method is invoked.
       */
      @Override
      public void removeAllViews() {
          throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
      }
  
      @Override
      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
          mLayoutHeight = getHeight();
      }
  
      /**
       * Return the position of the currently selected item within the adapter's data set
       *
       * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
       */
      @ViewDebug.CapturedViewProperty
      public int getSelectedItemPosition() {
          return mNextSelectedPosition;
      }
  
      /**
       * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
       * if nothing is selected.
       */
      @ViewDebug.CapturedViewProperty
      public long getSelectedItemId() {
          return mNextSelectedRowId;
      }
  
      /**
       * @return The view corresponding to the currently selected item, or null
       * if nothing is selected
       */
      public abstract View getSelectedView();
  
      /**
       * @return The data corresponding to the currently selected item, or
       * null if there is nothing selected.
       */
      public Object getSelectedItem() {
          T adapter = getAdapter();
          int selection = getSelectedItemPosition();
          if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
              return adapter.getItem(selection);
          } else {
              return null;
          }
      }
  
      /**
       * @return The number of items owned by the Adapter associated with this
       *         AdapterView. (This is the number of data items, which may be
       *         larger than the number of visible views.)
       */
      @ViewDebug.CapturedViewProperty
      public int getCount() {
          return mItemCount;
      }
  
      /**
       * Get the position within the adapter's data set for the view, where view is a an adapter item
       * or a descendant of an adapter item.
       *
       * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
       *        AdapterView at the time of the call.
       * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
       *         if the view does not correspond to a list item (or it is not currently visible).
       */
      public int getPositionForView(View view) {
          View listItem = view;
          try {
              View v;
              while (!(v = (View) listItem.getParent()).equals(this)) {
                  listItem = v;
              }
          } catch (ClassCastException e) {
              // We made it up to the window without find this list view
              return INVALID_POSITION;
          }
  
          // Search the children for the list item
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              if (getChildAt(i).equals(listItem)) {
                  return mFirstPosition + i;
              }
          }
  
          // Child not found!
          return INVALID_POSITION;
      }
  
      /**
       * Returns the position within the adapter's data set for the first item
       * displayed on screen.
       *
       * @return The position within the adapter's data set
       */
      public int getFirstVisiblePosition() {
          return mFirstPosition;
      }
  
      /**
       * Returns the position within the adapter's data set for the last item
       * displayed on screen.
       *
       * @return The position within the adapter's data set
       */
      public int getLastVisiblePosition() {
          return mFirstPosition + getChildCount() - 1;
      }
  
      /**
       * Sets the currently selected item. To support accessibility subclasses that
       * override this method must invoke the overriden super method first.
       *
       * @param position Index (starting at 0) of the data item to be selected.
       */
      public abstract void setSelection(int position);
  
      /**
       * Sets the view to show if the adapter is empty
       */
      public void setEmptyView(View emptyView) {
          mEmptyView = emptyView;
  
          final T adapter = getAdapter();
          final boolean empty = ((adapter == null) || adapter.isEmpty());
          updateEmptyStatus(empty);
      }
  
      /**
       * When the current adapter is empty, the AdapterView can display a special view
       * call the empty view. The empty view is used to provide feedback to the user
       * that no data is available in this AdapterView.
       *
       * @return The view to show if the adapter is empty.
       */
      public View getEmptyView() {
          return mEmptyView;
      }
  
      /**
       * Indicates whether this view is in filter mode. Filter mode can for instance
       * be enabled by a user when typing on the keyboard.
       *
       * @return True if the view is in filter mode, false otherwise.
       */
      boolean isInFilterMode() {
          return false;
      }
  
      @Override
      public void setFocusable(boolean focusable) {
          final T adapter = getAdapter();
          final boolean empty = adapter == null || adapter.getCount() == 0;
  
          mDesiredFocusableState = focusable;
          if (!focusable) {
              mDesiredFocusableInTouchModeState = false;
          }
  
          super.setFocusable(focusable && (!empty || isInFilterMode()));
      }
  
      @Override
      public void setFocusableInTouchMode(boolean focusable) {
          final T adapter = getAdapter();
          final boolean empty = adapter == null || adapter.getCount() == 0;
  
          mDesiredFocusableInTouchModeState = focusable;
          if (focusable) {
              mDesiredFocusableState = true;
          }
  
          super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
      }
  
      void checkFocus() {
          final T adapter = getAdapter();
          final boolean empty = adapter == null || adapter.getCount() == 0;
          final boolean focusable = !empty || isInFilterMode();
          // The order in which we set focusable in touch mode/focusable may matter
          // for the client, see View.setFocusableInTouchMode() comments for more
          // details
          super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
          super.setFocusable(focusable && mDesiredFocusableState);
          if (mEmptyView != null) {
              updateEmptyStatus((adapter == null) || adapter.isEmpty());
          }
      }
  
      /**
       * Update the status of the list based on the empty parameter.  If empty is true and
       * we have an empty view, display it.  In all the other cases, make sure that the listview
       * is VISIBLE and that the empty view is GONE (if it's not null).
       */
      private void updateEmptyStatus(boolean empty) {
          if (isInFilterMode()) {
              empty = false;
          }
  
          if (empty) {
              if (mEmptyView != null) {
                  mEmptyView.setVisibility(View.VISIBLE);
                  setVisibility(View.GONE);
              } else {
                  // If the caller just removed our empty view, make sure the list view is visible
                  setVisibility(View.VISIBLE);
              }
  
              // We are now GONE, so pending layouts will not be dispatched.
              // Force one here to make sure that the state of the list matches
              // the state of the adapter.
              if (mDataChanged) {
                  this.onLayout(false, getLeft(), getTop(), getRight(), getBottom());
              }
          } else {
              if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
              setVisibility(View.VISIBLE);
          }
      }
  
      /**
       * Gets the data associated with the specified position in the list.
       *
       * @param position Which data to get
       * @return The data associated with the specified position in the list
       */
      public Object getItemAtPosition(int position) {
          T adapter = getAdapter();
          return (adapter == null || position < 0) ? null : adapter.getItem(position);
      }
  
      public long getItemIdAtPosition(int position) {
          T adapter = getAdapter();
          return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
      }
  
      @Override
      public void setOnClickListener(OnClickListener l) {
          throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
                  + "You probably want setOnItemClickListener instead");
      }
  
      /**
       * Override to prevent freezing of any views created by the adapter.
       */
      @Override
      protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
          dispatchFreezeSelfOnly(container);
      }
  
      /**
       * Override to prevent thawing of any views created by the adapter.
       */
      @Override
      protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
          dispatchThawSelfOnly(container);
      }
  
      class AdapterDataSetObserver extends DataSetObserver {
  
          private Parcelable mInstanceState = null;
  
          @Override
          public void onChanged() {
              mDataChanged = true;
              mOldItemCount = mItemCount;
              mItemCount = getAdapter().getCount();
  
              // Detect the case where a cursor that was previously invalidated has
              // been repopulated with new data.
              if (IcsAdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                      && mOldItemCount == 0 && mItemCount > 0) {
                  IcsAdapterView.this.onRestoreInstanceState(mInstanceState);
                  mInstanceState = null;
              } else {
                  rememberSyncState();
              }
              checkFocus();
              requestLayout();
          }
  
          @Override
          public void onInvalidated() {
              mDataChanged = true;
  
              if (IcsAdapterView.this.getAdapter().hasStableIds()) {
                  // Remember the current state for the case where our hosting activity is being
                  // stopped and later restarted
                  mInstanceState = IcsAdapterView.this.onSaveInstanceState();
              }
  
              // Data is invalid so we should reset our state
              mOldItemCount = mItemCount;
              mItemCount = 0;
              mSelectedPosition = INVALID_POSITION;
              mSelectedRowId = INVALID_ROW_ID;
              mNextSelectedPosition = INVALID_POSITION;
              mNextSelectedRowId = INVALID_ROW_ID;
              mNeedSync = false;
  
              checkFocus();
              requestLayout();
          }
  
          public void clearSavedState() {
              mInstanceState = null;
          }
      }
  
      @Override
      protected void onDetachedFromWindow() {
          super.onDetachedFromWindow();
          removeCallbacks(mSelectionNotifier);
      }
  
      private class SelectionNotifier implements Runnable {
          public void run() {
              if (mDataChanged) {
                  // Data has changed between when this SelectionNotifier
                  // was posted and now. We need to wait until the AdapterView
                  // has been synched to the new data.
                  if (getAdapter() != null) {
                      post(this);
                  }
              } else {
                  fireOnSelected();
              }
          }
      }
  
      void selectionChanged() {
          if (mOnItemSelectedListener != null) {
              if (mInLayout || mBlockLayoutRequests) {
                  // If we are in a layout traversal, defer notification
                  // by posting. This ensures that the view tree is
                  // in a consistent state and is able to accomodate
                  // new layout or invalidate requests.
                  if (mSelectionNotifier == null) {
                      mSelectionNotifier = new SelectionNotifier();
                  }
                  post(mSelectionNotifier);
              } else {
                  fireOnSelected();
              }
          }
  
          // we fire selection events here not in View
          if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
              sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
          }
      }
  
      private void fireOnSelected() {
          if (mOnItemSelectedListener == null)
              return;
  
          int selection = this.getSelectedItemPosition();
          if (selection >= 0) {
              View v = getSelectedView();
              mOnItemSelectedListener.onItemSelected(this, v, selection,
                      getAdapter().getItemId(selection));
          } else {
              mOnItemSelectedListener.onNothingSelected(this);
          }
      }
  
      @Override
      public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
          View selectedView = getSelectedView();
          if (selectedView != null && selectedView.getVisibility() == VISIBLE
                  && selectedView.dispatchPopulateAccessibilityEvent(event)) {
              return true;
          }
          return false;
      }
  
      @Override
      public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
          if (super.onRequestSendAccessibilityEvent(child, event)) {
              // Add a record for ourselves as well.
              AccessibilityEvent record = AccessibilityEvent.obtain();
              onInitializeAccessibilityEvent(record);
              // Populate with the text of the requesting child.
              child.dispatchPopulateAccessibilityEvent(record);
              event.appendRecord(record);
              return true;
          }
          return false;
      }
  
      @Override
      public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
          super.onInitializeAccessibilityNodeInfo(info);
          info.setScrollable(isScrollableForAccessibility());
          View selectedView = getSelectedView();
          if (selectedView != null) {
              info.setEnabled(selectedView.isEnabled());
          }
      }
  
      @Override
      public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
          super.onInitializeAccessibilityEvent(event);
          event.setScrollable(isScrollableForAccessibility());
          View selectedView = getSelectedView();
          if (selectedView != null) {
              event.setEnabled(selectedView.isEnabled());
          }
          event.setCurrentItemIndex(getSelectedItemPosition());
          event.setFromIndex(getFirstVisiblePosition());
          event.setToIndex(getLastVisiblePosition());
          event.setItemCount(getCount());
      }
  
      private boolean isScrollableForAccessibility() {
          T adapter = getAdapter();
          if (adapter != null) {
              final int itemCount = adapter.getCount();
              return itemCount > 0
                  && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1);
          }
          return false;
      }
  
      @Override
      protected boolean canAnimate() {
          return super.canAnimate() && mItemCount > 0;
      }
  
      void handleDataChanged() {
          final int count = mItemCount;
          boolean found = false;
  
          if (count > 0) {
  
              int newPos;
  
              // Find the row we are supposed to sync to
              if (mNeedSync) {
                  // Update this first, since setNextSelectedPositionInt inspects
                  // it
                  mNeedSync = false;
  
                  // See if we can find a position in the new data with the same
                  // id as the old selection
                  newPos = findSyncPosition();
                  if (newPos >= 0) {
                      // Verify that new selection is selectable
                      int selectablePos = lookForSelectablePosition(newPos, true);
                      if (selectablePos == newPos) {
                          // Same row id is selected
                          setNextSelectedPositionInt(newPos);
                          found = true;
                      }
                  }
              }
              if (!found) {
                  // Try to use the same position if we can't find matching data
                  newPos = getSelectedItemPosition();
  
                  // Pin position to the available range
                  if (newPos >= count) {
                      newPos = count - 1;
                  }
                  if (newPos < 0) {
                      newPos = 0;
                  }
  
                  // Make sure we select something selectable -- first look down
                  int selectablePos = lookForSelectablePosition(newPos, true);
                  if (selectablePos < 0) {
                      // Looking down didn't work -- try looking up
                      selectablePos = lookForSelectablePosition(newPos, false);
                  }
                  if (selectablePos >= 0) {
                      setNextSelectedPositionInt(selectablePos);
                      checkSelectionChanged();
                      found = true;
                  }
              }
          }
          if (!found) {
              // Nothing is selected
              mSelectedPosition = INVALID_POSITION;
              mSelectedRowId = INVALID_ROW_ID;
              mNextSelectedPosition = INVALID_POSITION;
              mNextSelectedRowId = INVALID_ROW_ID;
              mNeedSync = false;
              checkSelectionChanged();
          }
      }
  
      void checkSelectionChanged() {
          if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
              selectionChanged();
              mOldSelectedPosition = mSelectedPosition;
              mOldSelectedRowId = mSelectedRowId;
          }
      }
  
      /**
       * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
       * and then alternates between moving up and moving down until 1) we find the right position, or
       * 2) we run out of time, or 3) we have looked at every position
       *
       * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
       *         be found
       */
      int findSyncPosition() {
          int count = mItemCount;
  
          if (count == 0) {
              return INVALID_POSITION;
          }
  
          long idToMatch = mSyncRowId;
          int seed = mSyncPosition;
  
          // If there isn't a selection don't hunt for it
          if (idToMatch == INVALID_ROW_ID) {
              return INVALID_POSITION;
          }
  
          // Pin seed to reasonable values
          seed = Math.max(0, seed);
          seed = Math.min(count - 1, seed);
  
          long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
  
          long rowId;
  
          // first position scanned so far
          int first = seed;
  
          // last position scanned so far
          int last = seed;
  
          // True if we should move down on the next iteration
          boolean next = false;
  
          // True when we have looked at the first item in the data
          boolean hitFirst;
  
          // True when we have looked at the last item in the data
          boolean hitLast;
  
          // Get the item ID locally (instead of getItemIdAtPosition), so
          // we need the adapter
          T adapter = getAdapter();
          if (adapter == null) {
              return INVALID_POSITION;
          }
  
          while (SystemClock.uptimeMillis() <= endTime) {
              rowId = adapter.getItemId(seed);
              if (rowId == idToMatch) {
                  // Found it!
                  return seed;
              }
  
              hitLast = last == count - 1;
              hitFirst = first == 0;
  
              if (hitLast && hitFirst) {
                  // Looked at everything
                  break;
              }
  
              if (hitFirst || (next && !hitLast)) {
                  // Either we hit the top, or we are trying to move down
                  last++;
                  seed = last;
                  // Try going up next time
                  next = false;
              } else if (hitLast || (!next && !hitFirst)) {
                  // Either we hit the bottom, or we are trying to move up
                  first--;
                  seed = first;
                  // Try going down next time
                  next = true;
              }
  
          }
  
          return INVALID_POSITION;
      }
  
      /**
       * Find a position that can be selected (i.e., is not a separator).
       *
       * @param position The starting position to look at.
       * @param lookDown Whether to look down for other positions.
       * @return The next selectable position starting at position and then searching either up or
       *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
       */
      int lookForSelectablePosition(int position, boolean lookDown) {
          return position;
      }
  
      /**
       * Utility to keep mSelectedPosition and mSelectedRowId in sync
       * @param position Our current position
       */
      void setSelectedPositionInt(int position) {
          mSelectedPosition = position;
          mSelectedRowId = getItemIdAtPosition(position);
      }
  
      /**
       * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
       * @param position Intended value for mSelectedPosition the next time we go
       * through layout
       */
      void setNextSelectedPositionInt(int position) {
          mNextSelectedPosition = position;
          mNextSelectedRowId = getItemIdAtPosition(position);
          // If we are trying to sync to the selection, update that too
          if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
              mSyncPosition = position;
              mSyncRowId = mNextSelectedRowId;
          }
      }
  
      /**
       * Remember enough information to restore the screen state when the data has
       * changed.
       *
       */
      void rememberSyncState() {
          if (getChildCount() > 0) {
              mNeedSync = true;
              mSyncHeight = mLayoutHeight;
              if (mSelectedPosition >= 0) {
                  // Sync the selection state
                  View v = getChildAt(mSelectedPosition - mFirstPosition);
                  mSyncRowId = mNextSelectedRowId;
                  mSyncPosition = mNextSelectedPosition;
                  if (v != null) {
                      mSpecificTop = v.getTop();
                  }
                  mSyncMode = SYNC_SELECTED_POSITION;
              } else {
                  // Sync the based on the offset of the first view
                  View v = getChildAt(0);
                  T adapter = getAdapter();
                  if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
                      mSyncRowId = adapter.getItemId(mFirstPosition);
                  } else {
                      mSyncRowId = NO_ID;
                  }
                  mSyncPosition = mFirstPosition;
                  if (v != null) {
                      mSpecificTop = v.getTop();
                  }
                  mSyncMode = SYNC_FIRST_POSITION;
              }
          }
      }
  }
  