content: /*
   * A modified version of the Android AdapterView
   *
   * Copyright 2012 Jess Anders
   *
   * 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.
   */
  
  /*
   * 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.jess.ui;
  
  
  import android.content.Context;
  import android.database.DataSetObserver;
  import android.os.Handler;
  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.ContextMenu.ContextMenuInfo;
  import android.widget.Adapter;
  
  
  /**
   * A TwoWayAdapterView is a view whose children are determined by an {@link Adapter}.
   *
   * <p>
   * See {@link TwoWayAbsListView} and {@link TwoWayGridView} for commonly used subclasses of TwoWayAdapterView.
   */
  public abstract class TwoWayAdapterView<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;
  
  	protected Context mContext = null;
  	
  	/**
  	 * Whether the view displays its items vertically or horizontally
  	 */
  	protected boolean mIsVertical = true;
  
  	/**
  	 * The position of the first child displayed
  	 */
  	@ViewDebug.ExportedProperty
  	int mFirstPosition = 0;
  
  	/**
  	 * The offset in pixels from the top of the AdapterView to the top or left
  	 * 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;
  
  	/**
  	 * Size of the view when mSyncPosition and mSyncRowId where set
  	 */
  	long mSyncSize;
  
  	/**
  	 * 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;
  
  	/**
  	 * Our width after the last layout
  	 */
  	private int mLayoutWidth;
  
  	/**
  	 * 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
  	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
  	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
  	int mItemCount;
  
  	/**
  	 * The number of items in the adapter before a data changed event occured.
  	 */
  	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 TwoWayAdapterView(Context context) {
  		super(context);
  		mContext = context;
  	}
  
  	public TwoWayAdapterView(Context context, AttributeSet attrs) {
  		super(context, attrs);
  		mContext = context;
  	}
  
  	public TwoWayAdapterView(Context context, AttributeSet attrs, int defStyle) {
  		super(context, attrs, defStyle);
  		mContext = context;
  	}
  
  
  	/**
  	 * Interface definition for a callback to be invoked when an item in this
  	 * AdapterView has been clicked.
  	 */
  	public interface OnItemClickListener {
  
  		/**
  		 * Callback method to be invoked when an item in this AdapterView has
  		 * been clicked.
  		 * <p>
  		 * Implementers can call getItemAtPosition(position) if they need
  		 * to access the data associated with the selected item.
  		 *
  		 * @param parent The AdapterView where the click happened.
  		 * @param view The view within the AdapterView that was clicked (this
  		 *            will be a view provided by the adapter)
  		 * @param position The position of the view in the adapter.
  		 * @param id The row id of the item that was clicked.
  		 */
  		void onItemClick(TwoWayAdapterView<?> parent, View view, int position, long id);
  	}
  
  	/**
  	 * 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);
  			mOnItemClickListener.onItemClick(this, 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(TwoWayAdapterView<?> 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 {
  		/**
  		 * Callback method to be invoked when an item in this view has been
  		 * selected.
  		 *
  		 * 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(TwoWayAdapterView<?> 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(TwoWayAdapterView<?> 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();
  		mLayoutWidth = getWidth();
  	}
  
  	/**
  	 * 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 view.)
  	 */
  	@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 (TwoWayAdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
  					&& mOldItemCount == 0 && mItemCount > 0) {
  				TwoWayAdapterView.this.onRestoreInstanceState(mInstanceState);
  				mInstanceState = null;
  			} else {
  				rememberSyncState();
  			}
  			checkFocus();
  			requestLayout();
  		}
  
  		@Override
  		public void onInvalidated() {
  			mDataChanged = true;
  
  			if (TwoWayAdapterView.this.getAdapter().hasStableIds()) {
  				// Remember the current state for the case where our hosting activity is being
  				// stopped and later restarted
  				mInstanceState = TwoWayAdapterView.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;
  			checkSelectionChanged();
  
  			checkFocus();
  			requestLayout();
  		}
  
  		public void clearSavedState() {
  			mInstanceState = null;
  		}
  	}
  
  	private class SelectionNotifier extends Handler 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.
  				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();
  				}
  				mSelectionNotifier.post(mSelectionNotifier);
  			} else {
  				fireOnSelected();
  			}
  		}
  
  		// we fire selection events here not in View
  		/* taken out for backward compatibility
  		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);
  		}
  	}
  
  	/* taken out for backward compatibility
  	@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
  		// events so we change the event type here.
  		if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
  			event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
  		}
  
  		// 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);
  		}
  
  		if (!populated) {
  			if (selectedView != null) {
  				event.setEnabled(selectedView.isEnabled());
  			}
  			event.setItemCount(getCount());
  			event.setCurrentItemIndex(getSelectedItemPosition());
  		}
  
  		return populated;
  	}*/
  
  	@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) {
  				// 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;
  			if (mIsVertical) {
  				mSyncSize = mLayoutHeight;
  			} else {
  				mSyncSize = mLayoutWidth;
  			}
  			if (mSelectedPosition >= 0) {
  				// Sync the selection state
  				View v = getChildAt(mSelectedPosition - mFirstPosition);
  				mSyncRowId = mNextSelectedRowId;
  				mSyncPosition = mNextSelectedPosition;
  				if (v != null) {
  					if (mIsVertical) {
  						mSpecificTop = v.getTop();
  					} else {
  						mSpecificTop = v.getLeft();
  					}
  				}
  				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) {
  					if (mIsVertical) {
  						mSpecificTop = v.getTop();
  					} else {
  						mSpecificTop = v.getLeft();
  					}
  				}
  				mSyncMode = SYNC_FIRST_POSITION;
  			}
  		}
  	}
  
  	/**
  	 * Offset the vertical location of all children of this view by the specified number of pixels.
  	 *
  	 * @param offset the number of pixels to offset
  	 *
  	 */
  	public void offsetChildrenTopAndBottom(int offset) {
  		final int count = getChildCount();
  
  		for (int i = 0; i < count; i++) {
  			final View v = getChildAt(i);
  			v.offsetTopAndBottom(offset);
  		}
  	}
  
  	/**
  	 * Offset the horizontal location of all children of this view by the specified number of pixels.
  	 *
  	 * @param offset the number of pixels to offset
  	 *
  	 */
  	public void offsetChildrenLeftAndRight(int offset) {
  		final int count = getChildCount();
  
  		for (int i = 0; i < count; i++) {
  			final View v = getChildAt(i);
  			v.offsetLeftAndRight(offset);
  		}
  	}
  
  	protected void setIsVertical(boolean vertical) {
  		mIsVertical = vertical;
  	}
  
  	protected boolean isVertical() {
  		return mIsVertical;
  	}
  	
  
  }
  