content: /*
   * A modified version of the Android GridView that can be configured to
   * scroll vertically or horizontally
   *
   * 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.content.res.TypedArray;
  import android.graphics.Rect;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.Gravity;
  import android.view.KeyEvent;
  import android.view.SoundEffectConstants;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.animation.GridLayoutAnimationController;
  import android.widget.ListAdapter;
  
  import com.amirarcane.recentimages.R;
  
  
  /**
   * A view that shows items in two-dimensional scrolling grid. The items in the
   * grid come from the {@link ListAdapter} associated with this view.
   *
   */
  public class TwoWayGridView extends TwoWayAbsListView {
  	public static final int NO_STRETCH = 0;
  	public static final int STRETCH_SPACING = 1;
  	public static final int STRETCH_COLUMN_WIDTH = 2;
  	public static final int STRETCH_SPACING_UNIFORM = 3;
  
  	public static final int AUTO_FIT = -1;
  
  	public static final String TAG = "TwoWayGridView";
  	public static final boolean DEBUG = false;
  	public static final String M_FIRST_POSITION = " mFirstPosition: ";
  	public static final String MAKE_AND_ADD_VIEW_END_POSITION = "makeAndAddView() - end - position: ";
  
  	private int mNumColumns = AUTO_FIT;
  	private int mNumRows = AUTO_FIT;
  
  	private int mHorizontalSpacing = 0;
  	private int mRequestedHorizontalSpacing;
  	private int mVerticalSpacing = 0;
  	private int mRequestedVerticalSpacing;
  	private int mStretchMode = STRETCH_COLUMN_WIDTH;
  	private int mColumnWidth;
  	private int mRequestedColumnWidth;
  	private int mRequestedNumColumns;
  	private int mRowHeight;
  	private int mRequestedRowHeight;
  	private int mRequestedNumRows;
  
  	private View mReferenceView = null;
  	private View mReferenceViewInSelectedRow = null;
  
  	private int mGravity = Gravity.LEFT;
  
  	private final Rect mTempRect = new Rect();
  
  	protected GridBuilder mGridBuilder = null;
  
  	public TwoWayGridView(Context context) {
  		super(context);
  		setupGridType();
  	}
  
  	public TwoWayGridView(Context context, AttributeSet attrs) {
  		this(context, attrs, android.R.attr.gridViewStyle);
  	}
  
  	public TwoWayGridView(Context context, AttributeSet attrs, int defStyle) {
  		super(context, attrs, defStyle);
  
  		TypedArray a = context.obtainStyledAttributes(attrs,
  				R.styleable.TwoWayGridView, defStyle, 0);
  
  		int hSpacing = a.getDimensionPixelOffset(
  				R.styleable.TwoWayGridView_horizontalSpacing, 0);
  		setHorizontalSpacing(hSpacing);
  
  		int vSpacing = a.getDimensionPixelOffset(
  				R.styleable.TwoWayGridView_verticalSpacing, 0);
  		setVerticalSpacing(vSpacing);
  
  		int index = a.getInt(R.styleable.TwoWayGridView_stretchMode, STRETCH_COLUMN_WIDTH);
  		if (index >= 0) {
  			setStretchMode(index);
  		}
  
  		int columnWidth = a.getDimensionPixelOffset(R.styleable.TwoWayGridView_columnWidth, -1);
  		if (columnWidth > 0) {
  			setColumnWidth(columnWidth);
  		}
  
  		int rowHeight = a.getDimensionPixelOffset(R.styleable.TwoWayGridView_rowHeight, -1);
  		if (rowHeight > 0) {
  			setRowHeight(rowHeight);
  		}
  
  		int numColumns = a.getInt(R.styleable.TwoWayGridView_numColumns, 1);
  		setNumColumns(numColumns);
  
  		int numRows = a.getInt(R.styleable.TwoWayGridView_numRows, 1);
  		setNumRows(numRows);
  
  		index = a.getInt(R.styleable.TwoWayGridView_gravity, -1);
  		if (index >= 0) {
  			setGravity(index);
  		}
  
  		a.recycle();
  		setupGridType();
  	}
  
  	private void setupGridType() {
  		if (mScrollVertically) {
  			mGridBuilder = new VerticalGridBuilder();
  		} else {
  			mGridBuilder = new HorizontalGridBuilder();
  		}
  	}
  
  	@Override
  	public ListAdapter getAdapter() {
  		return mAdapter;
  	}
  
  	/**
  	 * Sets the data behind this TwoWayGridView.
  	 *
  	 * @param adapter the adapter providing the grid's data
  	 */
  	@Override
  	public void setAdapter(ListAdapter adapter) {
  		if (null != mAdapter) {
  			mAdapter.unregisterDataSetObserver(mDataSetObserver);
  		}
  
  		resetList();
  		mRecycler.clear();
  		mAdapter = adapter;
  
  		mOldSelectedPosition = INVALID_POSITION;
  		mOldSelectedRowId = INVALID_ROW_ID;
  
  		if (mAdapter != null) {
  			mOldItemCount = mItemCount;
  			mItemCount = mAdapter.getCount();
  			mDataChanged = true;
  			checkFocus();
  
  			mDataSetObserver = new AdapterDataSetObserver();
  			mAdapter.registerDataSetObserver(mDataSetObserver);
  
  			mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
  
  			int position;
  			if (mStackFromBottom) {
  				position = lookForSelectablePosition(mItemCount - 1, false);
  			} else {
  				position = lookForSelectablePosition(0, true);
  			}
  			setSelectedPositionInt(position);
  			setNextSelectedPositionInt(position);
  			checkSelectionChanged();
  		} else {
  			checkFocus();
  			// Nothing selected
  			checkSelectionChanged();
  		}
  
  		requestLayout();
  	}
  
  	@Override
  	int lookForSelectablePosition(int position, boolean lookDown) {
  		final ListAdapter adapter = mAdapter;
  		if (adapter == null || isInTouchMode()) {
  			return INVALID_POSITION;
  		}
  
  		if (position < 0 || position >= mItemCount) {
  			return INVALID_POSITION;
  		}
  		return position;
  	}
  
  	/**
  	 * {@inheritDoc}
  	 */
  	@Override
  	void fillGap(boolean down) {
  		if (DEBUG) Log.i(TAG, "fillGap() down: " + down);
  		mGridBuilder.fillGap(down);
  	}
  
  	@Override
  	int findMotionRowY(int y) {
  		final int childCount = getChildCount();
  		if (childCount > 0) {
  
  			final int numColumns = mNumColumns;
  			if (!mStackFromBottom) {
  				for (int i = 0; i < childCount; i += numColumns) {
  					if (y <= getChildAt(i).getBottom()) {
  						return mFirstPosition + i;
  					}
  				}
  			} else {
  				for (int i = childCount - 1; i >= 0; i -= numColumns) {
  					if (y >= getChildAt(i).getTop()) {
  						return mFirstPosition + i;
  					}
  				}
  			}
  		}
  		return INVALID_POSITION;
  	}
  
  	@Override
  	int findMotionRowX(int x) {
  		final int childCount = getChildCount();
  		if (childCount > 0) {
  
  			final int numRows = mNumRows;
  			if (!mStackFromBottom) {
  				for (int i = 0; i < childCount; i += numRows) {
  					if (x <= getChildAt(i).getRight()) {
  						return mFirstPosition + i;
  					}
  				}
  			} else {
  				for (int i = childCount - 1; i >= 0; i -= numRows) {
  					if (x >= getChildAt(i).getLeft()) {
  						return mFirstPosition + i;
  					}
  				}
  			}
  		}
  		return INVALID_POSITION;
  	}
  
  
  
  	@Override
  	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  		if ((mScrollVertically && !(mGridBuilder instanceof VerticalGridBuilder))
  			|| (!mScrollVertically && !(mGridBuilder instanceof HorizontalGridBuilder)) ) {
  			setupGridType();
  		}
  		// Sets up mListPadding
  		mGridBuilder.onMeasure(widthMeasureSpec, heightMeasureSpec);
  	}
  
  	//TODO implement horizontal support
  	@Override
  	protected void attachLayoutAnimationParameters(View child,
  			ViewGroup.LayoutParams params, int index, int count) {
  
  		GridLayoutAnimationController.AnimationParameters animationParams =
  			(GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;
  
  		if (animationParams == null) {
  			animationParams = new GridLayoutAnimationController.AnimationParameters();
  			params.layoutAnimationParameters = animationParams;
  		}
  
  		animationParams.count = count;
  		animationParams.index = index;
  		animationParams.columnsCount = mNumColumns;
  		animationParams.rowsCount = count / mNumColumns;
  
  		if (!mStackFromBottom) {
  			animationParams.column = index % mNumColumns;
  			animationParams.row = index / mNumColumns;
  		} else {
  			final int invertedIndex = count - 1 - index;
  
  			animationParams.column = mNumColumns - 1 - (invertedIndex % mNumColumns);
  			animationParams.row = animationParams.rowsCount - 1 - invertedIndex / mNumColumns;
  		}
  	}
  
  	@Override
  	protected void layoutChildren() {
  		final boolean blockLayoutRequests = mBlockLayoutRequests;
  		if (!blockLayoutRequests) {
  			mBlockLayoutRequests = true;
  		}
  
  		try {
  			super.layoutChildren();
  
  			invalidate();
  
  			if (mAdapter == null) {
  				resetList();
  				invokeOnItemScrollListener();
  				return;
  			}
  
  			mGridBuilder.layoutChildren();
  
  		} finally {
  			if (!blockLayoutRequests) {
  				mBlockLayoutRequests = false;
  			}
  		}
  	}
  
  
  
  	/**
  	 * Sets the currently selected item
  	 * 
  	 * @param position Index (starting at 0) of the data item to be selected.
  	 * 
  	 * If in touch mode, the item will not be selected but it will still be positioned
  	 * appropriately.
  	 */
  	@Override
  	public void setSelection(int position) {
  		if (!isInTouchMode()) {
  			setNextSelectedPositionInt(position);
  		} else {
  			mResurrectToPosition = position;
  		}
  		mLayoutMode = LAYOUT_SET_SELECTION;
  		requestLayout();
  	}
  
  	/**
  	 * Makes the item at the supplied position selected.
  	 *
  	 * @param position the position of the new selection
  	 */
  	@Override
  	void setSelectionInt(int position) {
  		mGridBuilder.setSelectionInt(position);
  	}
  
  	@Override
  	public boolean onKeyDown(int keyCode, KeyEvent event) {
  		return commonKey(keyCode, 1, event);
  	}
  
  	@Override
  	public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
  		return commonKey(keyCode, repeatCount, event);
  	}
  
  	@Override
  	public boolean onKeyUp(int keyCode, KeyEvent event) {
  		return commonKey(keyCode, 1, event);
  	}
  
  	private boolean commonKey(int keyCode, int count, KeyEvent event) {
  		if (mAdapter == null) {
  			return false;
  		}
  
  		if (mDataChanged) {
  			layoutChildren();
  		}
  
  		boolean handled = false;
  		int action = event.getAction();
  
  		if (action != KeyEvent.ACTION_UP) {
  			if (mSelectedPosition < 0) {
  				switch (keyCode) {
  				case KeyEvent.KEYCODE_DPAD_UP:
  				case KeyEvent.KEYCODE_DPAD_DOWN:
  				case KeyEvent.KEYCODE_DPAD_LEFT:
  				case KeyEvent.KEYCODE_DPAD_RIGHT:
  				case KeyEvent.KEYCODE_DPAD_CENTER:
  				case KeyEvent.KEYCODE_SPACE:
  				case KeyEvent.KEYCODE_ENTER:
  					resurrectSelection();
  					return true;
  				}
  			}
  
  			switch (keyCode) {
  			case KeyEvent.KEYCODE_DPAD_LEFT:
  				if (!event.isAltPressed()) {
  					handled = mGridBuilder.arrowScroll(FOCUS_LEFT);
  				} else {
  					handled = fullScroll(FOCUS_UP);
  				}
  				break;
  
  			case KeyEvent.KEYCODE_DPAD_RIGHT:
  				if (!event.isAltPressed()) {
  					handled = mGridBuilder.arrowScroll(FOCUS_RIGHT);
  				} else {
  					handled = fullScroll(FOCUS_DOWN);
  				}
  				break;
  
  			case KeyEvent.KEYCODE_DPAD_UP:
  				if (!event.isAltPressed()) {
  					handled = mGridBuilder.arrowScroll(FOCUS_UP);
  
  				} else {
  					handled = fullScroll(FOCUS_UP);
  				}
  				break;
  
  			case KeyEvent.KEYCODE_DPAD_DOWN:
  				if (!event.isAltPressed()) {
  					handled = mGridBuilder.arrowScroll(FOCUS_DOWN);
  				} else {
  					handled = fullScroll(FOCUS_DOWN);
  				}
  				break;
  
  			case KeyEvent.KEYCODE_DPAD_CENTER:
  			case KeyEvent.KEYCODE_ENTER: {
  				if (getChildCount() > 0 && event.getRepeatCount() == 0) {
  					keyPressed();
  				}
  
  				return true;
  			}
  
  			case KeyEvent.KEYCODE_SPACE:
  				//if (mPopup == null || !mPopup.isShowing()) {
  				if (!event.isShiftPressed()) {
  					handled = pageScroll(FOCUS_DOWN);
  				} else {
  					handled = pageScroll(FOCUS_UP);
  				}
  				//}
  				break;
  			}
  		}
  
  		//if (!handled) {
  		//    handled = sendToTextFilter(keyCode, count, event);
  		//}
  
  		if (handled) {
  			return true;
  		} else {
  			switch (action) {
  			case KeyEvent.ACTION_DOWN:
  				return super.onKeyDown(keyCode, event);
  			case KeyEvent.ACTION_UP:
  				return super.onKeyUp(keyCode, event);
  			case KeyEvent.ACTION_MULTIPLE:
  				return super.onKeyMultiple(keyCode, count, event);
  			default:
  				return false;
  			}
  		}
  	}
  
  	/**
  	 * Scrolls up or down by the number of items currently present on screen.
  	 *
  	 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
  	 * @return whether selection was moved
  	 */
  	boolean pageScroll(int direction) {
  		int nextPage = -1;
  		//TODO this doesn't look correct...
  		if (direction == FOCUS_UP) {
  			nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
  		} else if (direction == FOCUS_DOWN) {
  			nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
  		}
  
  		if (nextPage >= 0) {
  			setSelectionInt(nextPage);
  			invokeOnItemScrollListener();
  			//awakenScrollBars();
  			return true;
  		}
  
  		return false;
  	}
  
  	/**
  	 * Go to the last or first item if possible.
  	 *
  	 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}.
  	 *
  	 * @return Whether selection was moved.
  	 */
  	boolean fullScroll(int direction) {
  		boolean moved = false;
  		if (direction == FOCUS_UP) {
  			mLayoutMode = LAYOUT_SET_SELECTION;
  			setSelectionInt(0);
  			invokeOnItemScrollListener();
  			moved = true;
  		} else if (direction == FOCUS_DOWN) {
  			mLayoutMode = LAYOUT_SET_SELECTION;
  			setSelectionInt(mItemCount - 1);
  			invokeOnItemScrollListener();
  			moved = true;
  		}
  
  		if (moved) {
  			//awakenScrollBars();
  		}
  
  		return moved;
  	}
  
  
  
  	@Override
  	protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
  		super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
  
  		int closestChildIndex = -1;
  		if (gainFocus && previouslyFocusedRect != null) {
  			previouslyFocusedRect.offset(getScrollX(), getScrollY());
  
  			// figure out which item should be selected based on previously
  			// focused rect
  			Rect otherRect = mTempRect;
  			int minDistance = Integer.MAX_VALUE;
  			final int childCount = getChildCount();
  			for (int i = 0; i < childCount; i++) {
  				// only consider view's on appropriate edge of grid
  				if (!mGridBuilder.isCandidateSelection(i, direction)) {
  					continue;
  				}
  
  				final View other = getChildAt(i);
  				other.getDrawingRect(otherRect);
  				offsetDescendantRectToMyCoords(other, otherRect);
  				int distance = getDistance(previouslyFocusedRect, otherRect, direction);
  
  				if (distance < minDistance) {
  					minDistance = distance;
  					closestChildIndex = i;
  				}
  			}
  		}
  
  		if (closestChildIndex >= 0) {
  			setSelection(closestChildIndex + mFirstPosition);
  		} else {
  			requestLayout();
  		}
  	}
  
  
  
  	/**
  	 * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
  	 *
  	 * @param gravity the gravity to apply to this grid's children
  	 *
  	 * @attr ref android.R.styleable#JessGridView_gravity
  	 */
  	public void setGravity(int gravity) {
  		if (mGravity != gravity) {
  			mGravity = gravity;
  			requestLayoutIfNecessary();
  		}
  	}
  
  	/**
  	 * Set the amount of horizontal (x) spacing to place between each item
  	 * in the grid.
  	 *
  	 * @param horizontalSpacing The amount of horizontal space between items,
  	 * in pixels.
  	 *
  	 * @attr ref android.R.styleable#JessGridView_horizontalSpacing
  	 */
  	public void setHorizontalSpacing(int horizontalSpacing) {
  		if (horizontalSpacing != mRequestedHorizontalSpacing) {
  			mRequestedHorizontalSpacing = horizontalSpacing;
  			requestLayoutIfNecessary();
  		}
  	}
  
  
  	/**
  	 * Set the amount of vertical (y) spacing to place between each item
  	 * in the grid.
  	 *
  	 * @param verticalSpacing The amount of vertical space between items,
  	 * in pixels.
  	 *
  	 * @attr ref android.R.styleable#JessGridView_verticalSpacing
  	 */
  	public void setVerticalSpacing(int verticalSpacing) {
  		if (verticalSpacing != mRequestedVerticalSpacing) {
  			mRequestedVerticalSpacing = verticalSpacing;
  			requestLayoutIfNecessary();
  		}
  	}
  
  	/**
  	 * Control how items are stretched to fill their space.
  	 *
  	 * @param stretchMode Either {@link #NO_STRETCH},
  	 * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
  	 *
  	 * @attr ref android.R.styleable#JessGridView_stretchMode
  	 */
  	public void setStretchMode(int stretchMode) {
  		if (stretchMode != mStretchMode) {
  			mStretchMode = stretchMode;
  			requestLayoutIfNecessary();
  		}
  	}
  
  	public int getStretchMode() {
  		return mStretchMode;
  	}
  
  	/**
  	 * Set the width of columns in the grid.  (Only used in vertical scroll mode)
  	 *
  	 * @param columnWidth The column width, in pixels.
  	 *
  	 * @attr ref android.R.styleable#JessGridView_columnWidth
  	 */
  	public void setColumnWidth(int columnWidth) {
  		if (columnWidth != mRequestedColumnWidth) {
  			mRequestedColumnWidth = columnWidth;
  			requestLayoutIfNecessary();
  		}
  	}
  
  	/**
  	 * Set the height of rows in the grid.  (Only used in horizontal scroll mode)
  	 *
  	 * @param rowHeight The row height, in pixels.
  	 *
  	 * @attr ref android.R.styleable#JessGridView_rowHeight
  	 */
  	public void setRowHeight(int rowHeight) {
  		if (rowHeight != mRequestedRowHeight) {
  			mRequestedRowHeight = rowHeight;
  			requestLayoutIfNecessary();
  		}
  	}
  
  	/**
  	 * Set the number of columns in the grid
  	 *
  	 * @param numColumns The desired number of columns.
  	 *
  	 * @attr ref android.R.styleable#JessGridView_numColumns
  	 */
  	public void setNumColumns(int numColumns) {
  		if (numColumns != mRequestedNumColumns) {
  			mRequestedNumColumns = numColumns;
  			requestLayoutIfNecessary();
  		}
  	}
  
  	/**
  	 * Set the number of rows in the grid
  	 *
  	 * @param numRows The desired number of rows.
  	 *
  	 * @attr ref android.R.styleable#JessGridView_numRows
  	 */
  	public void setNumRows(int numRows) {
  		if (numRows != mRequestedNumRows) {
  			mRequestedNumRows = numRows;
  			requestLayoutIfNecessary();
  		}
  	}
  
  
  
  	@Override
  	protected int computeVerticalScrollExtent() {
  		final int count = getChildCount();
  		if (count > 0 && mScrollVertically) {
  			final int numColumns = mNumColumns;
  			final int rowCount = (count + numColumns - 1) / numColumns;
  
  			int extent = rowCount * 100;
  
  			View view = getChildAt(0);
  			final int top = view.getTop();
  			int height = view.getHeight();
  			if (height > 0) {
  				extent += (top * 100) / height;
  			}
  
  			view = getChildAt(count - 1);
  			final int bottom = view.getBottom();
  			height = view.getHeight();
  			if (height > 0) {
  				extent -= ((bottom - getHeight()) * 100) / height;
  			}
  
  			return extent;
  		}
  		return 0;
  	}
  
  	@Override
  	protected int computeVerticalScrollOffset() {
  		if (mFirstPosition >= 0 && getChildCount() > 0 && mScrollVertically) {
  			final View view = getChildAt(0);
  			final int top = view.getTop();
  			int height = view.getHeight();
  			if (height > 0) {
  				final int numColumns = mNumColumns;
  				final int whichRow = mFirstPosition / numColumns;
  				final int rowCount = (mItemCount + numColumns - 1) / numColumns;
  				return Math.max(whichRow * 100 - (top * 100) / height +
  						(int) ((float) getScrollY() / getHeight() * rowCount * 100), 0);
  			}
  		}
  		return 0;
  	}
  
  	@Override
  	protected int computeVerticalScrollRange() {
  		// TODO: Account for vertical spacing too
  		if (!mScrollVertically) {
  			return 0;
  		}
  		final int numColumns = mNumColumns;
  		final int rowCount = (mItemCount + numColumns - 1) / numColumns;
  		return Math.max(rowCount * 100, 0);
  	}
  
  	@Override
  	protected int computeHorizontalScrollExtent() {
  		final int count = getChildCount();
  		if (count > 0 && !mScrollVertically) {
  			final int numRows = mNumRows;
  			final int columnCount = (count + numRows - 1) / numRows;
  
  			int extent = columnCount * 100;
  
  			View view = getChildAt(0);
  			final int left = view.getLeft();
  			int width = view.getWidth();
  			if (width > 0) {
  				extent += (left * 100) / width;
  			}
  
  			view = getChildAt(count - 1);
  			final int right = view.getRight();
  			width = view.getWidth();
  			if (width > 0) {
  				extent -= ((right - getWidth()) * 100) / width;
  			}
  
  			return extent;
  		}
  		return 0;
  	}
  
  	@Override
  	protected int computeHorizontalScrollOffset() {
  		if (mFirstPosition >= 0 && getChildCount() > 0 && !mScrollVertically) {
  			final View view = getChildAt(0);
  			final int left = view.getLeft();
  			int width = view.getWidth();
  			if (width > 0) {
  				final int numRows = mNumRows;
  				final int whichColumn = mFirstPosition / numRows;
  				final int columnCount = (mItemCount + numRows - 1) / numRows;
  				return Math.max(whichColumn * 100 - (left * 100) / width +
  						(int) ((float) getScrollX() / getWidth() * columnCount * 100), 0);
  			}
  		}
  		return 0;
  	}
  
  	@Override
  	protected int computeHorizontalScrollRange() {
  		// TODO: Account for horizontal spacing too
  		if (mScrollVertically) {
  			return 0;
  		}
  		final int numRows = mNumRows;
  		final int columnCount = (mItemCount + numRows - 1) / numRows;
  		return Math.max(columnCount * 100, 0);
  	}
  
  
  	private abstract class GridBuilder {
  
  		protected abstract View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
  				boolean selected, int where);
  
  		protected abstract void fillGap(boolean down);
  
  
  		protected abstract void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
  
  		protected abstract void layoutChildren();
  
  		protected abstract void setSelectionInt(int position);
  
  		protected abstract boolean arrowScroll(int direction);
  
  		protected abstract boolean isCandidateSelection(int childIndex, int direction);
  	}
  
  	private class VerticalGridBuilder extends GridBuilder {
  
  		public static final String CHILDREN_BOTTOM = " childrenBottom: ";
  
  		/**
  		 * Obtain the view and add it to our list of children. The view can be made
  		 * fresh, converted from an unused view, or used as is if it was in the
  		 * recycle bin.
  		 *
  		 * @param position Logical position in the list
  		 * @param y Top or bottom edge of the view to add
  		 * @param flow if true, align top edge to y. If false, align bottom edge to
  		 *        y.
  		 * @param childrenLeft Left edge where children should be positioned
  		 * @param selected Is this position selected?
  		 * @param where to add new item in the list
  		 * @return View that was added
  		 */
  		@Override
  		protected View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
  				boolean selected, int where) {
  			View child;
  			if (DEBUG) Log.i(TAG, "makeAndAddView() - start - position: " + position + M_FIRST_POSITION + mFirstPosition);
  			if (!mDataChanged) {
  				// Try to use an existing view for this position
  				child = mRecycler.getActiveView(position);
  				if (child != null) {
  					// Found it -- we're using an existing child
  					// This just needs to be positioned
  					setupChild(child, position, y, flow, childrenLeft, selected, true, where);
  					if (DEBUG) Log.i(TAG, MAKE_AND_ADD_VIEW_END_POSITION + position + "reused a view");
  					return child;
  				}
  			}
  
  			// Make a new view for this position, or convert an unused view if
  			// possible
  			child = obtainView(position, mIsScrap);
  
  			// This needs to be positioned and measured
  			setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
  			if (DEBUG) Log.i(TAG, MAKE_AND_ADD_VIEW_END_POSITION + position + "did NOT reuse a view - scrap: " + mIsScrap[0]);
  			return child;
  		}
  
  		@Override
  		protected void fillGap(boolean down) {
  
  			final int numColumns = mNumColumns;
  			final int verticalSpacing = mVerticalSpacing;
  
  			final int count = getChildCount();
  
  			if (down) {
  				final int startOffset = count > 0 ?
  						getChildAt(count - 1).getBottom() + verticalSpacing : getListPaddingTop();
  						int position = mFirstPosition + count;
  						if (mStackFromBottom) {
  							position += numColumns - 1;
  						}
  						fillDown(position, startOffset);
  						correctTooHigh(numColumns, verticalSpacing, getChildCount());
  			} else {
  				final int startOffset = count > 0 ?
  						getChildAt(0).getTop() - verticalSpacing : getHeight() - getListPaddingBottom();
  						int position = mFirstPosition;
  						if (!mStackFromBottom) {
  							position -= numColumns;
  						} else {
  							position--;
  						}
  						fillUp(position, startOffset);
  						correctTooLow(numColumns, verticalSpacing, getChildCount());
  			}
  		}
  
  		/**
  		 * Fills the list from pos down to the end of the list view.
  		 *
  		 * @param pos The first position to put in the list
  		 *
  		 * @param nextTop The location where the top of the item associated with pos
  		 *        should be drawn
  		 *
  		 * @return The view that is currently selected, if it happens to be in the
  		 *         range that we draw.
  		 */
  		private View fillDown(int pos, int nextTop) {
  			if (DEBUG) Log.i(TAG, "fillDown() pos: " + pos + " nextTop: " + nextTop + M_FIRST_POSITION + mFirstPosition);
  			View selectedView = null;
  
  			final int end = (getBottom() - getTop()) - mListPadding.bottom;
  
  			while (nextTop < end && pos < mItemCount) {
  				View temp = makeRow(pos, nextTop, true);
  				if (temp != null) {
  					selectedView = temp;
  				}
  
  				// mReferenceView will change with each call to makeRow()
  				// do not cache in a local variable outside of this loop
  				nextTop = mReferenceView.getBottom() + mVerticalSpacing;
  
  				pos += mNumColumns;
  			}
  
  			return selectedView;
  		}
  
  		private View makeRow(int startPos, int y, boolean flow) {
  			if (DEBUG) Log.i(TAG, "makeRow() startPos: " + startPos + " y: " + y + " flow: " + flow + M_FIRST_POSITION + mFirstPosition);
  			final int columnWidth = mColumnWidth;
  			final int horizontalSpacing = mHorizontalSpacing;
  
  			int last;
  			int nextLeft = mListPadding.left +
  			((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
  
  			if (!mStackFromBottom) {
  				last = Math.min(startPos + mNumColumns, mItemCount);
  			} else {
  				last = startPos + 1;
  				startPos = Math.max(0, startPos - mNumColumns + 1);
  
  				if (last - startPos < mNumColumns) {
  					nextLeft += (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing);
  				}
  			}
  
  			View selectedView = null;
  
  			final boolean hasFocus = shouldShowSelector();
  			final boolean inClick = touchModeDrawsInPressedState();
  			final int selectedPosition = mSelectedPosition;
  
  			View child = null;
  			for (int pos = startPos; pos < last; pos++) {
  				// is this the selected item?
  				boolean selected = pos == selectedPosition;
  				// does the list view have focus or contain focus
  
  				final int where = flow ? -1 : pos - startPos;
  				child = makeAndAddView(pos, y, flow, nextLeft, selected, where);
  
  				nextLeft += columnWidth;
  				if (pos < last - 1) {
  					nextLeft += horizontalSpacing;
  				}
  
  				if (selected && (hasFocus || inClick)) {
  					selectedView = child;
  				}
  			}
  
  			mReferenceView = child;
  
  			if (selectedView != null) {
  				mReferenceViewInSelectedRow = mReferenceView;
  			}
  
  			return selectedView;
  		}
  
  		/**
  		 * Fills the list from pos up to the top of the list view.
  		 *
  		 * @param pos The first position to put in the list
  		 *
  		 * @param nextBottom The location where the bottom of the item associated
  		 *        with pos should be drawn
  		 *
  		 * @return The view that is currently selected
  		 */
  		private View fillUp(int pos, int nextBottom) {
  			if (DEBUG) Log.i(TAG, "fillLeft() pos: " + pos + " nextBottom: " + nextBottom + M_FIRST_POSITION + mFirstPosition);
  			View selectedView = null;
  
  			final int end = mListPadding.top;
  
  			while (nextBottom > end && pos >= 0) {
  
  				View temp = makeRow(pos, nextBottom, false);
  				if (temp != null) {
  					selectedView = temp;
  				}
  
  				nextBottom = mReferenceView.getTop() - mVerticalSpacing;
  
  				mFirstPosition = pos;
  
  				pos -= mNumColumns;
  			}
  
  			if (mStackFromBottom) {
  				mFirstPosition = Math.max(0, pos + 1);
  			}
  
  			return selectedView;
  		}
  
  		/**
  		 * Fills the list from top to bottom, starting with mFirstPosition
  		 *
  		 * @param nextTop The location where the top of the first item should be
  		 *        drawn
  		 *
  		 * @return The view that is currently selected
  		 */
  		private View fillFromTop(int nextTop) {
  			if (DEBUG) Log.i(TAG, "fillFromTop() nextLeft: " + nextTop + M_FIRST_POSITION + mFirstPosition);
  			mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
  			mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
  			if (mFirstPosition < 0) {
  				mFirstPosition = 0;
  			}
  			mFirstPosition -= mFirstPosition % mNumColumns;
  			return fillDown(mFirstPosition, nextTop);
  		}
  
  		private View fillFromBottom(int lastPosition, int nextBottom) {
  			if (DEBUG) Log.i(TAG, "fillFromBotom() lastPosition: " + lastPosition + " nextBottom: " + nextBottom + M_FIRST_POSITION + mFirstPosition);
  			lastPosition = Math.max(lastPosition, mSelectedPosition);
  			lastPosition = Math.min(lastPosition, mItemCount - 1);
  
  			final int invertedPosition = mItemCount - 1 - lastPosition;
  			lastPosition = mItemCount - 1 - (invertedPosition - (invertedPosition % mNumColumns));
  
  			return fillUp(lastPosition, nextBottom);
  		}
  
  		private View fillSelection(int childrenTop, int childrenBottom) {
  			if (DEBUG) Log.i(TAG, "fillSelection() childrenTop: " + childrenTop + CHILDREN_BOTTOM + childrenBottom + M_FIRST_POSITION + mFirstPosition);
  			final int selectedPosition = reconcileSelectedPosition();
  			final int numColumns = mNumColumns;
  			final int verticalSpacing = mVerticalSpacing;
  
  			int rowStart;
  			int rowEnd = -1;
  
  			if (!mStackFromBottom) {
  				rowStart = selectedPosition - (selectedPosition % numColumns);
  			} else {
  				final int invertedSelection = mItemCount - 1 - selectedPosition;
  
  				rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
  				rowStart = Math.max(0, rowEnd - numColumns + 1);
  			}
  
  			final int fadingEdgeLength = getVerticalFadingEdgeLength();
  			final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
  
  			final View sel = makeRow(mStackFromBottom ? rowEnd : rowStart, topSelectionPixel, true);
  			mFirstPosition = rowStart;
  
  			final View referenceView = mReferenceView;
  
  			if (!mStackFromBottom) {
  				fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
  				pinToBottom(childrenBottom);
  				fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
  				adjustViewsUpOrDown();
  			} else {
  				final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom,
  						fadingEdgeLength, numColumns, rowStart);
  				final int offset = bottomSelectionPixel - referenceView.getBottom();
  				offsetChildrenTopAndBottom(offset);
  				fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
  				pinToTop(childrenTop);
  				fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
  				adjustViewsUpOrDown();
  			}
  
  			return sel;
  		}
  
  
  		/**
  		 * Layout during a scroll that results from tracking motion events. Places
  		 * the mMotionPosition view at the offset specified by mMotionViewTop, and
  		 * then build surrounding views from there.
  		 *
  		 * @param position the position at which to start filling
  		 * @param top the top of the view at that position
  		 * @return The selected view, or null if the selected view is outside the
  		 *         visible area.
  		 */
  		private View fillSpecific(int position, int top) {
  			if (DEBUG) Log.i(TAG, "fillSpecific() position: " + position + " top: " + top + M_FIRST_POSITION + mFirstPosition);
  			final int numColumns = mNumColumns;
  
  			int motionRowStart;
  			int motionRowEnd = -1;
  
  			if (!mStackFromBottom) {
  				motionRowStart = position - (position % numColumns);
  			} else {
  				final int invertedSelection = mItemCount - 1 - position;
  
  				motionRowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
  				motionRowStart = Math.max(0, motionRowEnd - numColumns + 1);
  			}
  
  			final View temp = makeRow(mStackFromBottom ? motionRowEnd : motionRowStart, top, true);
  
  			// Possibly changed again in fillUp if we add rows above this one.
  			mFirstPosition = motionRowStart;
  
  			final View referenceView = mReferenceView;
  			// We didn't have anything to layout, bail out
  			if (referenceView == null) {
  				return null;
  			}
  
  			final int verticalSpacing = mVerticalSpacing;
  
  			View above;
  			View below;
  
  			if (!mStackFromBottom) {
  				above = fillUp(motionRowStart - numColumns, referenceView.getTop() - verticalSpacing);
  				adjustViewsUpOrDown();
  				below = fillDown(motionRowStart + numColumns, referenceView.getBottom() + verticalSpacing);
  				// Check if we have dragged the bottom of the grid too high
  				final int childCount = getChildCount();
  				if (childCount > 0) {
  					correctTooHigh(numColumns, verticalSpacing, childCount);
  				}
  			} else {
  				below = fillDown(motionRowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
  				adjustViewsUpOrDown();
  				above = fillUp(motionRowStart - 1, referenceView.getTop() - verticalSpacing);
  				// Check if we have dragged the bottom of the grid too high
  				final int childCount = getChildCount();
  				if (childCount > 0) {
  					correctTooLow(numColumns, verticalSpacing, childCount);
  				}
  			}
  
  			if (temp != null) {
  				return temp;
  			} else if (above != null) {
  				return above;
  			} else {
  				return below;
  			}
  		}
  
  		private void correctTooHigh(int numColumns, int verticalSpacing, int childCount) {
  			if (DEBUG) Log.i(TAG, "correctTooLeft() numColumns: " + numColumns + " verticalSpacing: " + verticalSpacing + M_FIRST_POSITION + mFirstPosition);
  			// First see if the last item is visible
  			final int lastPosition = mFirstPosition + childCount - 1;
  			if (lastPosition == mItemCount - 1 && childCount > 0) {
  				// Get the last child ...
  				final View lastChild = getChildAt(childCount - 1);
  
  				// ... and its bottom edge
  				final int lastBottom = lastChild.getBottom();
  				// This is bottom of our drawable area
  				final int end = (getBottom() - getTop()) - mListPadding.bottom;
  
  				// This is how far the bottom edge of the last view is from the bottom of the
  				// drawable area
  				int bottomOffset = end - lastBottom;
  
  				final View firstChild = getChildAt(0);
  				final int firstTop = firstChild.getTop();
  
  				// Make sure we are 1) Too high, and 2) Either there are more rows above the
  				// first row or the first row is scrolled off the top of the drawable area
  				if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top))  {
  					if (mFirstPosition == 0) {
  						// Don't pull the top too far down
  						bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
  					}
  
  					// Move everything down
  					offsetChildrenTopAndBottom(bottomOffset);
  					if (mFirstPosition > 0) {
  						// Fill the gap that was opened above mFirstPosition with more rows, if
  						// possible
  						fillUp(mFirstPosition - (mStackFromBottom ? 1 : numColumns),
  								firstChild.getTop() - verticalSpacing);
  						// Close up the remaining gap
  						adjustViewsUpOrDown();
  					}
  				}
  			}
  		}
  
  		private void correctTooLow(int numColumns, int verticalSpacing, int childCount) {
  			if (DEBUG) Log.i(TAG, "correctTooLow() numColumns: " + numColumns + " verticalSpacing: " + verticalSpacing + M_FIRST_POSITION + mFirstPosition);
  			if (mFirstPosition == 0 && childCount > 0) {
  				// Get the first child ...
  				final View firstChild = getChildAt(0);
  
  				// ... and its top edge
  				final int firstTop = firstChild.getTop();
  
  				// This is top of our drawable area
  				final int start = mListPadding.top;
  
  				// This is bottom of our drawable area
  				final int end = (getBottom() - getTop()) - mListPadding.bottom;
  
  				// This is how far the top edge of the first view is from the top of the
  				// drawable area
  				int topOffset = firstTop - start;
  				final View lastChild = getChildAt(childCount - 1);
  				final int lastBottom = lastChild.getBottom();
  				final int lastPosition = mFirstPosition + childCount - 1;
  
  				// Make sure we are 1) Too low, and 2) Either there are more rows below the
  				// last row or the last row is scrolled off the bottom of the drawable area
  				if (topOffset > 0 && (lastPosition < mItemCount - 1 || lastBottom > end))  {
  					if (lastPosition == mItemCount - 1 ) {
  						// Don't pull the bottom too far up
  						topOffset = Math.min(topOffset, lastBottom - end);
  					}
  
  					// Move everything up
  					offsetChildrenTopAndBottom(-topOffset);
  					if (lastPosition < mItemCount - 1) {
  						// Fill the gap that was opened below the last position with more rows, if
  						// possible
  						fillDown(lastPosition + (!mStackFromBottom ? 1 : numColumns),
  								lastChild.getBottom() + verticalSpacing);
  						// Close up the remaining gap
  						adjustViewsUpOrDown();
  					}
  				}
  			}
  		}
  
  		/**
  		 * Fills the grid based on positioning the new selection at a specific
  		 * location. The selection may be moved so that it does not intersect the
  		 * faded edges. The grid is then filled upwards and downwards from there.
  		 *
  		 * @param selectedTop Where the selected item should be
  		 * @param childrenTop Where to start drawing children
  		 * @param childrenBottom Last pixel where children can be drawn
  		 * @return The view that currently has selection
  		 */
  		private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
  			if (DEBUG) Log.i(TAG, "fillFromSelection() selectedTop: " + selectedTop + " childrenTop: " + childrenTop + CHILDREN_BOTTOM + childrenBottom + M_FIRST_POSITION + mFirstPosition);
  			final int fadingEdgeLength = getVerticalFadingEdgeLength();
  			final int selectedPosition = mSelectedPosition;
  			final int numColumns = mNumColumns;
  			final int verticalSpacing = mVerticalSpacing;
  
  			int rowStart;
  			int rowEnd = -1;
  
  			if (!mStackFromBottom) {
  				rowStart = selectedPosition - (selectedPosition % numColumns);
  			} else {
  				int invertedSelection = mItemCount - 1 - selectedPosition;
  
  				rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
  				rowStart = Math.max(0, rowEnd - numColumns + 1);
  			}
  
  			View sel;
  			View referenceView;
  
  			int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
  			int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
  					numColumns, rowStart);
  
  			sel = makeRow(mStackFromBottom ? rowEnd : rowStart, selectedTop, true);
  			// Possibly changed again in fillUp if we add rows above this one.
  			mFirstPosition = rowStart;
  
  			referenceView = mReferenceView;
  			adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
  			adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
  
  			if (!mStackFromBottom) {
  				fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
  				adjustViewsUpOrDown();
  				fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
  			} else {
  				fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
  				adjustViewsUpOrDown();
  				fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
  			}
  
  
  			return sel;
  		}
  
  
  		/**
  		 * Calculate the bottom-most pixel we can draw the selection into
  		 *
  		 * @param childrenBottom Bottom pixel were children can be drawn
  		 * @param fadingEdgeLength Length of the fading edge in pixels, if present
  		 * @param numColumns Number of columns in the grid
  		 * @param rowStart The start of the row that will contain the selection
  		 * @return The bottom-most pixel we can draw the selection into
  		 */
  		private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
  				int numColumns, int rowStart) {
  			// Last pixel we can draw the selection into
  			int bottomSelectionPixel = childrenBottom;
  			if (rowStart + numColumns - 1 < mItemCount - 1) {
  				bottomSelectionPixel -= fadingEdgeLength;
  			}
  			return bottomSelectionPixel;
  		}
  
  		/**
  		 * Calculate the top-most pixel we can draw the selection into
  		 *
  		 * @param childrenTop Top pixel were children can be drawn
  		 * @param fadingEdgeLength Length of the fading edge in pixels, if present
  		 * @param rowStart The start of the row that will contain the selection
  		 * @return The top-most pixel we can draw the selection into
  		 */
  		private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int rowStart) {
  			// first pixel we can draw the selection into
  			int topSelectionPixel = childrenTop;
  			if (rowStart > 0) {
  				topSelectionPixel += fadingEdgeLength;
  			}
  			return topSelectionPixel;
  		}
  
  		/**
  		 * Move all views upwards so the selected row does not interesect the bottom
  		 * fading edge (if necessary).
  		 *
  		 * @param childInSelectedRow A child in the row that contains the selection
  		 * @param topSelectionPixel The topmost pixel we can draw the selection into
  		 * @param bottomSelectionPixel The bottommost pixel we can draw the
  		 *        selection into
  		 */
  		private void adjustForBottomFadingEdge(View childInSelectedRow,
  				int topSelectionPixel, int bottomSelectionPixel) {
  			// Some of the newly selected item extends below the bottom of the
  			// list
  			if (childInSelectedRow.getBottom() > bottomSelectionPixel) {
  
  				// Find space available above the selection into which we can
  				// scroll upwards
  				int spaceAbove = childInSelectedRow.getTop() - topSelectionPixel;
  
  				// Find space required to bring the bottom of the selected item
  				// fully into view
  				int spaceBelow = childInSelectedRow.getBottom() - bottomSelectionPixel;
  				int offset = Math.min(spaceAbove, spaceBelow);
  
  				// Now offset the selected item to get it into view
  				offsetChildrenTopAndBottom(-offset);
  			}
  		}
  
  		/**
  		 * Move all views upwards so the selected row does not interesect the top
  		 * fading edge (if necessary).
  		 *
  		 * @param childInSelectedRow A child in the row that contains the selection
  		 * @param topSelectionPixel The topmost pixel we can draw the selection into
  		 * @param bottomSelectionPixel The bottommost pixel we can draw the
  		 *        selection into
  		 */
  		private void adjustForTopFadingEdge(View childInSelectedRow,
  				int topSelectionPixel, int bottomSelectionPixel) {
  			// Some of the newly selected item extends above the top of the list
  			if (childInSelectedRow.getTop() < topSelectionPixel) {
  				// Find space required to bring the top of the selected item
  				// fully into view
  				int spaceAbove = topSelectionPixel - childInSelectedRow.getTop();
  
  				// Find space available below the selection into which we can
  				// scroll downwards
  				int spaceBelow = bottomSelectionPixel - childInSelectedRow.getBottom();
  				int offset = Math.min(spaceAbove, spaceBelow);
  
  				// Now offset the selected item to get it into view
  				offsetChildrenTopAndBottom(offset);
  			}
  		}
  
  		private void determineColumns(int availableSpace) {
  			final int requestedHorizontalSpacing = mRequestedHorizontalSpacing;
  			final int stretchMode = mStretchMode;
  			final int requestedColumnWidth = mRequestedColumnWidth;
  			mVerticalSpacing = mRequestedVerticalSpacing;
  
  			if (mRequestedNumColumns == AUTO_FIT) {
  				if (requestedColumnWidth > 0) {
  					// Client told us to pick the number of columns
  					mNumColumns = (availableSpace + requestedHorizontalSpacing) /
  					(requestedColumnWidth + requestedHorizontalSpacing);
  				} else {
  					// Just make up a number if we don't have enough info
  					mNumColumns = 2;
  				}
  			} else {
  				// We picked the columns
  				mNumColumns = mRequestedNumColumns;
  			}
  
  			if (mNumColumns <= 0) {
  				mNumColumns = 1;
  			}
  
  			if (stretchMode == NO_STRETCH) {
  				// Nobody stretches
  				mColumnWidth = requestedColumnWidth;
  				mHorizontalSpacing = requestedHorizontalSpacing;
  			} else {
  				int spaceLeftOver;
  				switch (stretchMode) {
  					case STRETCH_COLUMN_WIDTH:
  						// Stretch the columns
  						spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth) -
  								((mNumColumns - 1) * requestedHorizontalSpacing);
  						mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
  						mHorizontalSpacing = requestedHorizontalSpacing;
  						break;
  
  					case STRETCH_SPACING:
  						// Stretch the spacing between columns
  						spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth) -
  								((mNumColumns - 1) * requestedHorizontalSpacing);
  						mColumnWidth = requestedColumnWidth;
  						if (mNumColumns > 1) {
  							mHorizontalSpacing = requestedHorizontalSpacing +
  									spaceLeftOver / (mNumColumns - 1);
  						} else {
  							mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
  						}
  						break;
  
  					case STRETCH_SPACING_UNIFORM:
  						// Stretch the spacing between columns
  						spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth) -
  								((mNumColumns + 1) * requestedHorizontalSpacing);
  						mColumnWidth = requestedColumnWidth;
  						if (mNumColumns > 1) {
  							mHorizontalSpacing = requestedHorizontalSpacing +
  									spaceLeftOver / (mNumColumns + 1);
  						} else {
  							mHorizontalSpacing = ((requestedHorizontalSpacing * 2) + spaceLeftOver) / 2;
  						}
  						break;
  				}
  			}
  		}
  
  		/**
  		 * Fills the grid based on positioning the new selection relative to the old
  		 * selection. The new selection will be placed at, above, or below the
  		 * location of the new selection depending on how the selection is moving.
  		 * The selection will then be pinned to the visible part of the screen,
  		 * excluding the edges that are faded. The grid is then filled upwards and
  		 * downwards from there.
  		 *
  		 * @param delta Which way we are moving
  		 * @param childrenTop Where to start drawing children
  		 * @param childrenBottom Last pixel where children can be drawn
  		 * @return The view that currently has selection
  		 */
  		private View moveSelection(int delta, int childrenTop, int childrenBottom) {
  			if (DEBUG) Log.i(TAG, "moveSelection() delta: " + delta + " childrenTop: " + childrenTop + CHILDREN_BOTTOM + childrenBottom + M_FIRST_POSITION + mFirstPosition);
  			final int fadingEdgeLength = getVerticalFadingEdgeLength();
  			final int selectedPosition = mSelectedPosition;
  			final int numColumns = mNumColumns;
  			final int verticalSpacing = mVerticalSpacing;
  
  			int oldRowStart;
  			int rowStart;
  			int rowEnd = -1;
  
  			if (!mStackFromBottom) {
  				oldRowStart = (selectedPosition - delta) - ((selectedPosition - delta) % numColumns);
  
  				rowStart = selectedPosition - (selectedPosition % numColumns);
  			} else {
  				int invertedSelection = mItemCount - 1 - selectedPosition;
  
  				rowEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
  				rowStart = Math.max(0, rowEnd - numColumns + 1);
  
  				invertedSelection = mItemCount - 1 - (selectedPosition - delta);
  				oldRowStart = mItemCount - 1 - (invertedSelection - (invertedSelection % numColumns));
  				oldRowStart = Math.max(0, oldRowStart - numColumns + 1);
  			}
  
  			final int rowDelta = rowStart - oldRowStart;
  
  			final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength, rowStart);
  			final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
  					numColumns, rowStart);
  
  			// Possibly changed again in fillUp if we add rows above this one.
  			mFirstPosition = rowStart;
  
  			View sel;
  			View referenceView;
  
  			if (rowDelta > 0) {
  				/*
  				 * Case 1: Scrolling down.
  				 */
  
  				final int oldBottom = mReferenceViewInSelectedRow == null ? 0 :
  					mReferenceViewInSelectedRow.getBottom();
  
  				sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldBottom + verticalSpacing, true);
  				referenceView = mReferenceView;
  
  				adjustForBottomFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
  			} else if (rowDelta < 0) {
  				/*
  				 * Case 2: Scrolling up.
  				 */
  				final int oldTop = mReferenceViewInSelectedRow == null ?
  						0 : mReferenceViewInSelectedRow .getTop();
  
  				sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop - verticalSpacing, false);
  				referenceView = mReferenceView;
  
  				adjustForTopFadingEdge(referenceView, topSelectionPixel, bottomSelectionPixel);
  			} else {
  				/*
  				 * Keep selection where it was
  				 */
  				final int oldTop = mReferenceViewInSelectedRow == null ?
  						0 : mReferenceViewInSelectedRow .getTop();
  
  				sel = makeRow(mStackFromBottom ? rowEnd : rowStart, oldTop, true);
  				referenceView = mReferenceView;
  			}
  
  			if (!mStackFromBottom) {
  				fillUp(rowStart - numColumns, referenceView.getTop() - verticalSpacing);
  				adjustViewsUpOrDown();
  				fillDown(rowStart + numColumns, referenceView.getBottom() + verticalSpacing);
  			} else {
  				fillDown(rowEnd + numColumns, referenceView.getBottom() + verticalSpacing);
  				adjustViewsUpOrDown();
  				fillUp(rowStart - 1, referenceView.getTop() - verticalSpacing);
  			}
  
  			return sel;
  		}
  
  		@Override
  		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  			int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  			int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  			int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  			int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  			if (DEBUG) Log.i(TAG, "vertical onMeasure heightMode: " + heightMode);
  			if (widthMode == MeasureSpec.UNSPECIFIED) {
  				if (mColumnWidth > 0) {
  					widthSize = mColumnWidth + mListPadding.left + mListPadding.right;
  				} else {
  					widthSize = mListPadding.left + mListPadding.right;
  				}
  				widthSize += getVerticalScrollbarWidth();
  			}
  
  			int childWidth = widthSize - mListPadding.left - mListPadding.right;
  			determineColumns(childWidth);
  
  			int childHeight = 0;
  
  			mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
  			final int count = mItemCount;
  			if (count > 0) {
  				final View child = obtainView(0, mIsScrap);
  
  				TwoWayAbsListView.LayoutParams p = (TwoWayAbsListView.LayoutParams)child.getLayoutParams();
  				if (p == null) {
  					p = new TwoWayAbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
  							ViewGroup.LayoutParams.WRAP_CONTENT, 0);
  					child.setLayoutParams(p);
  				}
  				p.viewType = mAdapter.getItemViewType(0);
  				p.forceAdd = true;
  
  				int childHeightSpec = getChildMeasureSpec(
  						MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
  				int childWidthSpec = getChildMeasureSpec(
  						MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
  				child.measure(childWidthSpec, childHeightSpec);
  
  				childHeight = child.getMeasuredHeight();
  
  				if (mRecycler.shouldRecycleViewType(p.viewType)) {
  					mRecycler.addScrapView(child);
  				}
  			}
  
  			if (heightMode == MeasureSpec.UNSPECIFIED) {
  				heightSize = mListPadding.top + mListPadding.bottom + childHeight +
  				getVerticalFadingEdgeLength() * 2;
  			}
  
  			if (heightMode == MeasureSpec.AT_MOST) {
  				int ourSize =  mListPadding.top + mListPadding.bottom;
  
  				final int numColumns = mNumColumns;
  				for (int i = 0; i < count; i += numColumns) {
  					ourSize += childHeight;
  					if (i + numColumns < count) {
  						ourSize += mVerticalSpacing;
  					}
  					if (ourSize >= heightSize) {
  						ourSize = heightSize;
  						break;
  					}
  				}
  				heightSize = ourSize;
  			}
  
  			setMeasuredDimension(widthSize, heightSize);
  			mWidthMeasureSpec = widthMeasureSpec;
  			if (DEBUG) Log.i(TAG, "Vertical onMeasure widthSize: " + widthSize + " heightSize: " + heightSize);
  		}
  
  		@Override
  		protected void layoutChildren() {
  			final int childrenTop = mListPadding.top;
  			final int childrenBottom = getBottom() - getTop() - mListPadding.bottom;
  
  			int childCount = getChildCount();
  			int index;
  			int delta = 0;
  
  			View sel;
  			View oldSel = null;
  			View oldFirst = null;
  			View newSel = null;
  
  			// Remember stuff we will need down below
  			switch (mLayoutMode) {
  			case LAYOUT_SET_SELECTION:
  				index = mNextSelectedPosition - mFirstPosition;
  				if (index >= 0 && index < childCount) {
  					newSel = getChildAt(index);
  				}
  				break;
  			case LAYOUT_FORCE_TOP:
  			case LAYOUT_FORCE_BOTTOM:
  			case LAYOUT_SPECIFIC:
  			case LAYOUT_SYNC:
  				break;
  			case LAYOUT_MOVE_SELECTION:
  				if (mNextSelectedPosition >= 0) {
  					delta = mNextSelectedPosition - mSelectedPosition;
  				}
  				break;
  			default:
  				// Remember the previously selected view
  				index = mSelectedPosition - mFirstPosition;
  				if (index >= 0 && index < childCount) {
  					oldSel = getChildAt(index);
  				}
  
  				// Remember the previous first child
  				oldFirst = getChildAt(0);
  			}
  
  			boolean dataChanged = mDataChanged;
  			if (dataChanged) {
  				handleDataChanged();
  			}
  
  			// Handle the empty set by removing all views that are visible
  			// and calling it a day
  			if (mItemCount == 0) {
  				resetList();
  				invokeOnItemScrollListener();
  				return;
  			}
  
  			setSelectedPositionInt(mNextSelectedPosition);
  
  			// Pull all children into the RecycleBin.
  			// These views will be reused if possible
  			final int firstPosition = mFirstPosition;
  			final RecycleBin recycleBin = mRecycler;
  
  			if (dataChanged) {
  				for (int i = 0; i < childCount; i++) {
  					recycleBin.addScrapView(getChildAt(i));
  				}
  			} else {
  				recycleBin.fillActiveViews(childCount, firstPosition);
  			}
  
  			// Clear out old views
  			//removeAllViewsInLayout();
  			detachAllViewsFromParent();
  
  			switch (mLayoutMode) {
  			case LAYOUT_SET_SELECTION:
  				if (newSel != null) {
  					sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
  				} else {
  					sel = fillSelection(childrenTop, childrenBottom);
  				}
  				break;
  			case LAYOUT_FORCE_TOP:
  				mFirstPosition = 0;
  				sel = fillFromTop(childrenTop);
  				adjustViewsUpOrDown();
  				break;
  			case LAYOUT_FORCE_BOTTOM:
  				sel = fillUp(mItemCount - 1, childrenBottom);
  				adjustViewsUpOrDown();
  				break;
  			case LAYOUT_SPECIFIC:
  				sel = fillSpecific(mSelectedPosition, mSpecificTop);
  				break;
  			case LAYOUT_SYNC:
  				sel = fillSpecific(mSyncPosition, mSpecificTop);
  				break;
  			case LAYOUT_MOVE_SELECTION:
  				// Move the selection relative to its old position
  				sel = moveSelection(delta, childrenTop, childrenBottom);
  				break;
  			default:
  				if (childCount == 0) {
  					if (!mStackFromBottom) {
  						setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
  								INVALID_POSITION : 0);
  						sel = fillFromTop(childrenTop);
  					} else {
  						final int last = mItemCount - 1;
  						setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
  								INVALID_POSITION : last);
  						sel = fillFromBottom(last, childrenBottom);
  					}
  				} else {
  					if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
  						sel = fillSpecific(mSelectedPosition, oldSel == null ?
  								childrenTop : oldSel.getTop());
  					} else if (mFirstPosition < mItemCount)  {
  						sel = fillSpecific(mFirstPosition, oldFirst == null ?
  								childrenTop : oldFirst.getTop());
  					} else {
  						sel = fillSpecific(0, childrenTop);
  					}
  				}
  				break;
  			}
  
  			// Flush any cached views that did not get reused above
  			recycleBin.scrapActiveViews();
  
  			if (sel != null) {
  				positionSelector(sel);
  				mSelectedTop = sel.getTop();
  			} else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
  				View child = getChildAt(mMotionPosition - mFirstPosition);
  				if (child != null) positionSelector(child);
  			} else {
  				mSelectedTop = 0;
  				mSelectorRect.setEmpty();
  			}
  
  			mLayoutMode = LAYOUT_NORMAL;
  			mDataChanged = false;
  			mNeedSync = false;
  			setNextSelectedPositionInt(mSelectedPosition);
  
  			updateScrollIndicators();
  
  			if (mItemCount > 0) {
  				checkSelectionChanged();
  			}
  
  			invokeOnItemScrollListener();
  
  		}
  
  		/**
  		 * Make sure views are touching the top or bottom edge, as appropriate for
  		 * our gravity
  		 */
  		private void adjustViewsUpOrDown() {
  			final int childCount = getChildCount();
  
  			if (childCount > 0) {
  				int delta;
  				View child;
  
  				if (!mStackFromBottom) {
  					// Uh-oh -- we came up short. Slide all views up to make them
  					// align with the top
  					child = getChildAt(0);
  					delta = child.getTop() - mListPadding.top;
  					if (mFirstPosition != 0) {
  						// It's OK to have some space above the first item if it is
  						// part of the vertical spacing
  						delta -= mVerticalSpacing;
  					}
  					if (delta < 0) {
  						// We only are looking to see if we are too low, not too high
  						delta = 0;
  					}
  				} else {
  					// we are too high, slide all views down to align with bottom
  					child = getChildAt(childCount - 1);
  					delta = child.getBottom() - (getHeight() - mListPadding.bottom);
  
  					if (mFirstPosition + childCount < mItemCount) {
  						// It's OK to have some space below the last item if it is
  						// part of the vertical spacing
  						delta += mVerticalSpacing;
  					}
  
  					if (delta > 0) {
  						// We only are looking to see if we are too high, not too low
  						delta = 0;
  					}
  				}
  
  				if (delta != 0) {
  					offsetChildrenTopAndBottom(-delta);
  				}
  			}
  		}
  
  		/**
  		 * Add a view as a child and make sure it is measured (if necessary) and
  		 * positioned properly.
  		 *
  		 * @param child The view to add
  		 * @param position The position of the view
  		 * @param y The y position relative to which this view will be positioned
  		 * @param flow if true, align top edge to y. If false, align bottom edge
  		 *        to y.
  		 * @param childrenLeft Left edge where children should be positioned
  		 * @param selected Is this position selected?
  		 * @param recycled Has this view been pulled from the recycle bin? If so it
  		 *        does not need to be remeasured.
  		 * @param where Where to add the item in the list
  		 *
  		 */
  		private void setupChild(View child, int position, int y, boolean flow, int childrenLeft,
  				boolean selected, boolean recycled, int where) {
  			boolean isSelected = selected && shouldShowSelector();
  			final boolean updateChildSelected = isSelected != child.isSelected();
  			final int mode = mTouchMode;
  			final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
  			mMotionPosition == position;
  			final boolean updateChildPressed = isPressed != child.isPressed();
  
  			boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
  
  			// Respect layout params that are already in the view. Otherwise make
  			// some up...
  			TwoWayAbsListView.LayoutParams p = (TwoWayAbsListView.LayoutParams)child.getLayoutParams();
  			if (p == null) {
  				p = new TwoWayAbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
  						ViewGroup.LayoutParams.WRAP_CONTENT, 0);
  			}
  			p.viewType = mAdapter.getItemViewType(position);
  
  			if (recycled && !p.forceAdd) {
  				attachViewToParent(child, where, p);
  			} else {
  				p.forceAdd = false;
  				addViewInLayout(child, where, p, true);
  			}
  
  			if (updateChildSelected) {
  				child.setSelected(isSelected);
  				if (isSelected) {
  					requestFocus();
  				}
  			}
  
  			if (updateChildPressed) {
  				child.setPressed(isPressed);
  			}
  
  			if (needToMeasure) {
  				int childHeightSpec = ViewGroup.getChildMeasureSpec(
  						MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
  
  				int childWidthSpec = ViewGroup.getChildMeasureSpec(
  						MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
  				child.measure(childWidthSpec, childHeightSpec);
  			} else {
  				cleanupLayoutState(child);
  			}
  
  			final int w = child.getMeasuredWidth();
  			final int h = child.getMeasuredHeight();
  
  			int childLeft;
  			final int childTop = flow ? y : y - h;
  
  			switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
  			case Gravity.LEFT:
  				childLeft = childrenLeft;
  				break;
  			case Gravity.CENTER_HORIZONTAL:
  				childLeft = childrenLeft + ((mColumnWidth - w) / 2);
  				break;
  			case Gravity.RIGHT:
  				childLeft = childrenLeft + mColumnWidth - w;
  				break;
  			default:
  				childLeft = childrenLeft;
  				break;
  			}
  
  			if (needToMeasure) {
  				final int childRight = childLeft + w;
  				final int childBottom = childTop + h;
  				child.layout(childLeft, childTop, childRight, childBottom);
  			} else {
  				child.offsetLeftAndRight(childLeft - child.getLeft());
  				child.offsetTopAndBottom(childTop - child.getTop());
  			}
  
  			if (mCachingStarted) {
  				child.setDrawingCacheEnabled(true);
  			}
  		}
  
  		private void pinToTop(int childrenTop) {
  			if (mFirstPosition == 0) {
  				final int top = getChildAt(0).getTop();
  				final int offset = childrenTop - top;
  				if (offset < 0) {
  					offsetChildrenTopAndBottom(offset);
  				}
  			}
  		}
  
  		private void pinToBottom(int childrenBottom) {
  			final int count = getChildCount();
  			if (mFirstPosition + count == mItemCount) {
  				final int bottom = getChildAt(count - 1).getBottom();
  				final int offset = childrenBottom - bottom;
  				if (offset > 0) {
  					offsetChildrenTopAndBottom(offset);
  				}
  			}
  		}
  
  		/**
  		 * Makes the item at the supplied position selected.
  		 *
  		 * @param position the position of the new selection
  		 */
  		@Override
  		protected void setSelectionInt(int position) {
  			int previousSelectedPosition = mNextSelectedPosition;
  
  			setNextSelectedPositionInt(position);
  			TwoWayGridView.this.layoutChildren();
  
  			final int next = mStackFromBottom ? mItemCount - 1  - mNextSelectedPosition :
  				mNextSelectedPosition;
  			final int previous = mStackFromBottom ? mItemCount - 1
  					- previousSelectedPosition : previousSelectedPosition;
  
  			final int nextRow = next / mNumColumns;
  			final int previousRow = previous / mNumColumns;
  
  			if (nextRow != previousRow) {
  				//awakenScrollBars();
  			}
  
  		}
  
  
  		/**
  		 * Scrolls to the next or previous item, horizontally or vertically.
  		 *
  		 * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
  		 *        {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
  		 *
  		 * @return whether selection was moved
  		 */
  		@Override
  		protected boolean arrowScroll(int direction) {
  			final int selectedPosition = mSelectedPosition;
  			final int numColumns = mNumColumns;
  
  			int startOfRowPos;
  			int endOfRowPos;
  
  			boolean moved = false;
  
  			if (!mStackFromBottom) {
  				startOfRowPos = (selectedPosition / numColumns) * numColumns;
  				endOfRowPos = Math.min(startOfRowPos + numColumns - 1, mItemCount - 1);
  			} else {
  				final int invertedSelection = mItemCount - 1 - selectedPosition;
  				endOfRowPos = mItemCount - 1 - (invertedSelection / numColumns) * numColumns;
  				startOfRowPos = Math.max(0, endOfRowPos - numColumns + 1);
  			}
  
  			switch (direction) {
  			case FOCUS_UP:
  				if (startOfRowPos > 0) {
  					mLayoutMode = LAYOUT_MOVE_SELECTION;
  					setSelectionInt(Math.max(0, selectedPosition - numColumns));
  					moved = true;
  				}
  				break;
  			case FOCUS_DOWN:
  				if (endOfRowPos < mItemCount - 1) {
  					mLayoutMode = LAYOUT_MOVE_SELECTION;
  					setSelectionInt(Math.min(selectedPosition + numColumns, mItemCount - 1));
  					moved = true;
  				}
  				break;
  			case FOCUS_LEFT:
  				if (selectedPosition > startOfRowPos) {
  					mLayoutMode = LAYOUT_MOVE_SELECTION;
  					setSelectionInt(Math.max(0, selectedPosition - 1));
  					moved = true;
  				}
  				break;
  			case FOCUS_RIGHT:
  				if (selectedPosition < endOfRowPos) {
  					mLayoutMode = LAYOUT_MOVE_SELECTION;
  					setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
  					moved = true;
  				}
  				break;
  			}
  
  			if (moved) {
  				playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
  				invokeOnItemScrollListener();
  			}
  
  			if (moved) {
  				//awakenScrollBars();
  			}
  
  			return moved;
  		}
  
  		/**
  		 * Is childIndex a candidate for next focus given the direction the focus
  		 * change is coming from?
  		 * @param childIndex The index to check.
  		 * @param direction The direction, one of
  		 *        {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}
  		 * @return Whether childIndex is a candidate.
  		 */
  		@Override
  		protected boolean isCandidateSelection(int childIndex, int direction) {
  			final int count = getChildCount();
  			final int invertedIndex = count - 1 - childIndex;
  
  			int rowStart;
  			int rowEnd;
  
  			if (!mStackFromBottom) {
  				rowStart = childIndex - (childIndex % mNumColumns);
  				rowEnd = Math.max(rowStart + mNumColumns - 1, count);
  			} else {
  				rowEnd = count - 1 - (invertedIndex - (invertedIndex % mNumColumns));
  				rowStart = Math.max(0, rowEnd - mNumColumns + 1);
  			}
  
  			switch (direction) {
  			case View.FOCUS_RIGHT:
  				// coming from left, selection is only valid if it is on left
  				// edge
  				return childIndex == rowStart;
  			case View.FOCUS_DOWN:
  				// coming from top; only valid if in top row
  				return rowStart == 0;
  			case View.FOCUS_LEFT:
  				// coming from right, must be on right edge
  				return childIndex == rowEnd;
  			case View.FOCUS_UP:
  				// coming from bottom, need to be in last row
  				return rowEnd == count - 1;
  			case View.FOCUS_FORWARD:
  				// coming from top-left, need to be first in top row
  				return childIndex == rowStart && rowStart == 0;
  			case View.FOCUS_BACKWARD:
  				// coming from bottom-right, need to be last in bottom row
  				return childIndex == rowEnd && rowEnd == count - 1;
  			default:
  				throw new IllegalArgumentException("direction must be one of "
  				  + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
  				  + "FOCUS_FORWARD, FOCUS_BACKWARD}");
  			}
  		}
  
  	}
  
  
  	/////////////////////////////////////////////////////////////////////////////////////////////////////
  	//
  	// Horizontal Grid Builder
  	//
  	/////////////////////////////////////////////////////////////////////////////////////////////////////
  	private class HorizontalGridBuilder extends GridBuilder {
  
  		public static final String CHILDREN_RIGHT = " childrenRight: ";
  
  		/**
  		 * Obtain the view and add it to our list of children. The view can be made
  		 * fresh, converted from an unused view, or used as is if it was in the
  		 * recycle bin.
  		 *
  		 * @param position Logical position in the list
  		 * @param x Left or Right edge of the view to add
  		 * @param flow if true, align left edge to x. If false, align right edge to
  		 *        x.
  		 * @param childrenTop Top edge where children should be positioned
  		 * @param selected Is this position selected?
  		 * @param where to add new item in the list
  		 * @return View that was added
  		 */
  		@Override
  		protected View makeAndAddView(int position, int x, boolean flow, int childrenTop,
  				boolean selected, int where) {
  			View child;
  
  			if (!mDataChanged) {
  				// Try to use an existing view for this position
  				child = mRecycler.getActiveView(position);
  				if (child != null) {
  					// Found it -- we're using an existing child
  					// This just needs to be positioned
  					setupChild(child, position, x, flow, childrenTop, selected, true, where);
  					if (DEBUG) Log.i(TAG, MAKE_AND_ADD_VIEW_END_POSITION + position + " reused a view");
  					return child;
  				}
  			}
  
  			// Make a new view for this position, or convert an unused view if
  			// possible
  			child = obtainView(position, mIsScrap);
  
  			// This needs to be positioned and measured
  			setupChild(child, position, x, flow, childrenTop, selected, mIsScrap[0], where);
  			if (DEBUG) Log.i(TAG, MAKE_AND_ADD_VIEW_END_POSITION + position + "did NOT reuse a view - scrap: " + mIsScrap[0]);
  			return child;
  		}
  
  		@Override
  		protected void fillGap(boolean right) {
  			final int numRows = mNumRows;
  			final int horizontalSpacing = mHorizontalSpacing;
  
  			final int count = getChildCount();
  
  			if (right) {
  				final int startOffset = count > 0 ?
  						getChildAt(count - 1).getRight() + horizontalSpacing : getListPaddingLeft();
  						int position = mFirstPosition + count;
  						if (mStackFromBottom) {
  							position += numRows - 1;
  						}
  						fillRight(position, startOffset);
  						correctTooLeft(numRows, horizontalSpacing, getChildCount());
  			} else {
  				final int startOffset = count > 0 ?
  						getChildAt(0).getLeft() - horizontalSpacing : getWidth() - getListPaddingRight();
  						int position = mFirstPosition;
  						if (!mStackFromBottom) {
  							position -= numRows;
  						} else {
  							position--;
  						}
  						fillLeft(position, startOffset);
  						correctTooRight(numRows, horizontalSpacing, getChildCount());
  			}
  		}
  
  
  		/**
  		 * Fills the list from pos right to the end of the list view.
  		 *
  		 * @param pos The first position to put in the list
  		 *
  		 * @param nextLeft The location where the left of the item associated with pos
  		 *        should be drawn
  		 *
  		 * @return The view that is currently selected, if it happens to be in the
  		 *         range that we draw.
  		 */
  		private View fillRight(int pos, int nextLeft) {
  			if (DEBUG) Log.i(TAG, "fillRight() pos: " + pos + " nextLeft: " + nextLeft + M_FIRST_POSITION + mFirstPosition);
  			View selectedView = null;
  
  			final int end = (getRight() - getLeft()) - mListPadding.right;
  
  			while (nextLeft < end && pos < mItemCount) {
  				View temp = makeColumn(pos, nextLeft, true);
  				if (temp != null) {
  					selectedView = temp;
  				}
  
  				// mReferenceView will change with each call to makeRow()
  				// do not cache in a local variable outside of this loop
  				nextLeft = mReferenceView.getRight() + mHorizontalSpacing;
  
  				pos += mNumRows;
  			}
  
  			return selectedView;
  		}
  
  		private View makeColumn(int startPos, int x, boolean flow) {
  			if (DEBUG) Log.i(TAG, "makeColumn() startPos: " + startPos + " x: " + x + " flow: " + flow + M_FIRST_POSITION + mFirstPosition);
  			final int rowHeight = mRowHeight;
  			final int verticalSpacing = mVerticalSpacing;
  
  			int last;
  			int nextTop = mListPadding.top +
  			((mStretchMode == STRETCH_SPACING_UNIFORM) ? verticalSpacing : 0);
  
  			if (!mStackFromBottom) {
  				last = Math.min(startPos + mNumRows, mItemCount);
  			} else {
  				last = startPos + 1;
  				startPos = Math.max(0, startPos - mNumRows + 1);
  
  				if (last - startPos < mNumRows) {
  					nextTop += (mNumRows - (last - startPos)) * (rowHeight + verticalSpacing);
  				}
  			}
  
  			View selectedView = null;
  
  			final boolean hasFocus = shouldShowSelector();
  			final boolean inClick = touchModeDrawsInPressedState();
  			final int selectedPosition = mSelectedPosition;
  
  			View child = null;
  			for (int pos = startPos; pos < last; pos++) {
  				// is this the selected item?
  				boolean selected = pos == selectedPosition;
  				// does the list view have focus or contain focus
  
  				final int where = flow ? -1 : pos - startPos;
  				child = makeAndAddView(pos, x, flow, nextTop, selected, where);
  
  				nextTop += rowHeight;
  				if (pos < last - 1) {
  					nextTop += verticalSpacing;
  				}
  
  				if (selected && (hasFocus || inClick)) {
  					selectedView = child;
  				}
  			}
  
  			mReferenceView = child;
  
  			if (selectedView != null) {
  				mReferenceViewInSelectedRow = mReferenceView;
  			}
  
  			return selectedView;
  		}
  
  
  		/**
  		 * Fills the list from pos to the left of the list view.
  		 *
  		 * @param pos The first position to put in the list
  		 *
  		 * @param nextRight The location where the right of the item associated
  		 *        with pos should be drawn
  		 *
  		 * @return The view that is currently selected
  		 */
  		private View fillLeft(int pos, int nextRight) {
  			if (DEBUG) Log.i(TAG, "fillLeft() pos: " + pos + " nextRight: " + nextRight + M_FIRST_POSITION + mFirstPosition);
  			View selectedView = null;
  
  			final int end = mListPadding.left;
  
  			while (nextRight > end && pos >= 0) {
  
  				View temp = makeColumn(pos, nextRight, false);
  				if (temp != null) {
  					selectedView = temp;
  				}
  
  				nextRight = mReferenceView.getLeft() - mHorizontalSpacing;
  
  				mFirstPosition = pos;
  
  				pos -= mNumRows;
  			}
  
  			if (mStackFromBottom) {
  				mFirstPosition = Math.max(0, pos + 1);
  			}
  
  			return selectedView;
  		}
  
  		/**
  		 * Fills the list from left to right, starting with mFirstPosition
  		 *
  		 * @param nextLeft The location where the left of the first item should be
  		 *        drawn
  		 *
  		 * @return The view that is currently selected
  		 */
  		private View fillFromTop(int nextLeft) {
  			if (DEBUG) Log.i(TAG, "fillFromTop() nextLeft: " + nextLeft + M_FIRST_POSITION + mFirstPosition);
  			mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
  			mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
  			if (mFirstPosition < 0) {
  				mFirstPosition = 0;
  			}
  			mFirstPosition -= mFirstPosition % mNumRows;
  			return fillRight(mFirstPosition, nextLeft);
  		}
  
  		private View fillFromBottom(int lastPosition, int nextRight) {
  			if (DEBUG) Log.i(TAG, "fillFromBotom() lastPosition: " + lastPosition + " nextRight: " + nextRight + M_FIRST_POSITION + mFirstPosition);
  			lastPosition = Math.max(lastPosition, mSelectedPosition);
  			lastPosition = Math.min(lastPosition, mItemCount - 1);
  
  			final int invertedPosition = mItemCount - 1 - lastPosition;
  			lastPosition = mItemCount - 1 - (invertedPosition - (invertedPosition % mNumRows));
  
  			return fillLeft(lastPosition, nextRight);
  		}
  
  		private View fillSelection(int childrenLeft, int childrenRight) {
  			if (DEBUG) Log.i(TAG, "fillSelection() childrenLeft: " + childrenLeft + CHILDREN_RIGHT + childrenRight + M_FIRST_POSITION + mFirstPosition);
  			final int selectedPosition = reconcileSelectedPosition();
  			final int numRows = mNumRows;
  			final int horizontalSpacing = mHorizontalSpacing;
  
  			int columnStart;
  			int columnEnd = -1;
  
  			if (!mStackFromBottom) {
  				columnStart = selectedPosition - (selectedPosition % numRows);
  			} else {
  				final int invertedSelection = mItemCount - 1 - selectedPosition;
  
  				columnEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numRows));
  				columnStart = Math.max(0, columnEnd - numRows + 1);
  			}
  
  			final int fadingEdgeLength = getHorizontalFadingEdgeLength();
  			final int leftSelectionPixel = getLeftSelectionPixel(childrenLeft, fadingEdgeLength, columnStart);
  
  			final View sel = makeColumn(mStackFromBottom ? columnEnd : columnStart, leftSelectionPixel, true);
  			mFirstPosition = columnStart;
  
  			final View referenceView = mReferenceView;
  
  			if (!mStackFromBottom) {
  				fillRight(columnStart + numRows, referenceView.getRight() + horizontalSpacing);
  				pinToRight(childrenRight);
  				fillLeft(columnStart - numRows, referenceView.getLeft() - horizontalSpacing);
  				adjustViewsLeftOrRight();
  			} else {
  				final int rightSelectionPixel = getRightSelectionPixel(childrenRight,
  						fadingEdgeLength, numRows, columnStart);
  				final int offset = rightSelectionPixel - referenceView.getRight();
  				offsetChildrenLeftAndRight(offset);
  				fillLeft(columnStart - 1, referenceView.getLeft() - horizontalSpacing);
  				pinToLeft(childrenLeft);
  				fillRight(columnEnd + numRows, referenceView.getRight() + horizontalSpacing);
  				adjustViewsLeftOrRight();
  			}
  
  			return sel;
  		}
  
  
  		/**
  		 * Layout during a scroll that results from tracking motion events. Places
  		 * the mMotionPosition view at the offset specified by mMotionViewLeft, and
  		 * then build surrounding views from there.
  		 *
  		 * @param position the position at which to start filling
  		 * @param left the left of the view at that position
  		 * @return The selected view, or null if the selected view is outside the
  		 *         visible area.
  		 */
  		private View fillSpecific(int position, int left) {
  			if (DEBUG) Log.i(TAG, "fillSpecific() position: " + position + " left: " + left + M_FIRST_POSITION + mFirstPosition);
  			final int numRows = mNumRows;
  
  			int motionColumnStart;
  			int motionColumnEnd = -1;
  
  			if (!mStackFromBottom) {
  				//TODO don't understand what this is doing....
  				motionColumnStart = position - (position % numRows);
  			} else {
  				//TODO don't understand what this is doing....
  				final int invertedSelection = mItemCount - 1 - position;
  
  				motionColumnEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numRows));
  				motionColumnStart = Math.max(0, motionColumnEnd - numRows + 1);
  			}
  
  			final View temp = makeColumn(mStackFromBottom ? motionColumnEnd : motionColumnStart, left, true);
  
  			// Possibly changed again in fillUp if we add rows above this one.
  			mFirstPosition = motionColumnStart;
  
  			final View referenceView = mReferenceView;
  			// We didn't have anything to layout, bail out
  			if (referenceView == null) {
  				return null;
  			}
  
  			final int horizontalSpacing = mHorizontalSpacing;
  
  			View leftOf;
  			View rightOf;
  
  			if (!mStackFromBottom) {
  				leftOf = fillLeft(motionColumnStart - numRows, referenceView.getLeft() - horizontalSpacing);
  				adjustViewsLeftOrRight();
  				rightOf = fillRight(motionColumnStart + numRows, referenceView.getRight() + horizontalSpacing);
  				// Check if we have dragged the bottom of the grid too high
  				final int childCount = getChildCount();
  				if (childCount > 0) {
  					correctTooLeft(numRows, horizontalSpacing, childCount);
  				}
  			} else {
  				rightOf = fillRight(motionColumnEnd + numRows, referenceView.getRight() + horizontalSpacing);
  				adjustViewsLeftOrRight();
  				leftOf = fillLeft(motionColumnStart - 1, referenceView.getLeft() - horizontalSpacing);
  				// Check if we have dragged the right of the grid too high
  				final int childCount = getChildCount();
  				if (childCount > 0) {
  					correctTooRight(numRows, horizontalSpacing, childCount);
  				}
  			}
  
  			if (temp != null) {
  				return temp;
  			} else if (leftOf != null) {
  				return leftOf;
  			} else {
  				return rightOf;
  			}
  		}
  
  
  		private void correctTooLeft(int numRows, int horizontalSpacing, int childCount) {
  			if (DEBUG) Log.i(TAG, "correctTooLeft() numRows: " + numRows + " horizontalSpacing: " + horizontalSpacing + M_FIRST_POSITION + mFirstPosition);
  			// First see if the last item is visible
  			final int lastPosition = mFirstPosition + childCount - 1;
  			if (lastPosition == mItemCount - 1 && childCount > 0) {
  				// Get the last child ...
  				final View lastChild = getChildAt(childCount - 1);
  
  				// ... and its right edge
  				final int lastRight = lastChild.getRight();
  				// This is end of our drawable area
  				final int end = (getRight() - getLeft()) - mListPadding.right;
  
  				// This is how far the right edge of the last view is from the right
  				// edge of the drawable area
  				int rightOffset = end - lastRight;
  
  				final View firstChild = getChildAt(0);
  				final int firstLeft = firstChild.getLeft();
  
  				// Make sure we are 1) Too Left, and 2) Either there are more columns to left of
  				// the first column or the first column is scrolled off the top of the drawable area
  				if (rightOffset > 0 && (mFirstPosition > 0 || firstLeft < mListPadding.left))  {
  					if (mFirstPosition == 0) {
  						// Don't pull the left too far right
  						rightOffset = Math.min(rightOffset, mListPadding.left - firstLeft);
  					}
  
  					// Move everything right
  					offsetChildrenLeftAndRight(rightOffset);
  					if (mFirstPosition > 0) {
  						// Fill the gap that was opened above mFirstPosition with more columns, if
  						// possible
  						fillLeft(mFirstPosition - (mStackFromBottom ? 1 : numRows),
  								firstChild.getLeft() - horizontalSpacing);
  						// Close up the remaining gap
  						adjustViewsLeftOrRight();
  					}
  				}
  			}
  		}
  
  		private void correctTooRight(int numRows, int horizontalSpacing, int childCount) {
  			if (DEBUG) Log.i(TAG, "correctTooRight() numRows: " + numRows + " horizontalSpacing: " + horizontalSpacing + M_FIRST_POSITION + mFirstPosition);
  			if (mFirstPosition == 0 && childCount > 0) {
  				// Get the first child ...
  				final View firstChild = getChildAt(0);
  
  				// ... and its left edge
  				final int firstLeft = firstChild.getLeft();
  
  				// This is left of our drawable area
  				final int start = mListPadding.left;
  
  				// This is right of our drawable area
  				final int end = (getRight() - getLeft()) - mListPadding.right;
  
  				// This is how far the left edge of the first view is from the left of the
  				// drawable area
  				int leftOffset = firstLeft - start;
  				final View lastChild = getChildAt(childCount - 1);
  				final int lastRight = lastChild.getRight();
  				final int lastPosition = mFirstPosition + childCount - 1;
  
  				// Make sure we are 1) Too right, and 2) Either there are more columns to right of the
  				// last column or the last column is scrolled off the right of the drawable area
  				if (leftOffset > 0 && (lastPosition < mItemCount - 1 || lastRight > end))  {
  					if (lastPosition == mItemCount - 1 ) {
  						// Don't pull the right too far left
  						leftOffset = Math.min(leftOffset, lastRight - end);
  					}
  
  					// Move everything left
  					offsetChildrenLeftAndRight(-leftOffset);
  					if (lastPosition < mItemCount - 1) {
  						// Fill the gap that was opened to right of the last position with
  						// more columns, if possible
  						fillRight(lastPosition + (!mStackFromBottom ? 1 : numRows),
  								lastChild.getRight() + horizontalSpacing);
  						// Close up the remaining gap
  						adjustViewsLeftOrRight();
  					}
  				}
  			}
  		}
  
  
  		/**
  		 * Fills the grid based on positioning the new selection relative to the old
  		 * selection. The new selection will be placed at, to left of, or to right of the
  		 * location of the new selection depending on how the selection is moving.
  		 * The selection will then be pinned to the visible part of the screen,
  		 * excluding the edges that are faded. The grid is then filled leftwards and
  		 * rightwards from there.
  		 *
  		 * @param delta Which way we are moving
  		 * @param childrenLeft Where to start drawing children
  		 * @param childrenRight Last pixel where children can be drawn
  		 * @return The view that currently has selection
  		 */
  		private View moveSelection(int delta, int childrenLeft, int childrenRight) {
  			if (DEBUG) Log.i(TAG, "moveSelection() delta: " + delta + " childrenLeft: " + childrenLeft + CHILDREN_RIGHT + childrenRight + M_FIRST_POSITION + mFirstPosition);
  			final int fadingEdgeLength = getHorizontalFadingEdgeLength();
  			final int selectedPosition = mSelectedPosition;
  			final int numRows = mNumRows;
  			final int horizontalSpacing = mHorizontalSpacing;
  
  			int oldColumnStart;
  			int columnStart;
  			int columnEnd = -1;
  
  			if (!mStackFromBottom) {
  				oldColumnStart = (selectedPosition - delta) - ((selectedPosition - delta) % numRows);
  
  				columnStart = selectedPosition - (selectedPosition % numRows);
  			} else {
  				int invertedSelection = mItemCount - 1 - selectedPosition;
  
  				columnEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numRows));
  				columnStart = Math.max(0, columnEnd - numRows + 1);
  
  				invertedSelection = mItemCount - 1 - (selectedPosition - delta);
  				oldColumnStart = mItemCount - 1 - (invertedSelection - (invertedSelection % numRows));
  				oldColumnStart = Math.max(0, oldColumnStart - numRows + 1);
  			}
  
  			final int rowDelta = columnStart - oldColumnStart;
  
  			final int leftSelectionPixel = getLeftSelectionPixel(childrenLeft, fadingEdgeLength, columnStart);
  			final int rightSelectionPixel = getRightSelectionPixel(childrenRight, fadingEdgeLength,
  					numRows, columnStart);
  
  			// Possibly changed again in fillLeft if we add rows above this one.
  			mFirstPosition = columnStart;
  
  			View sel;
  			View referenceView;
  
  			if (rowDelta > 0) {
  				/*
  				 * Case 1: Scrolling right.
  				 */
  
  				final int oldRight = mReferenceViewInSelectedRow == null ? 0 :
  					mReferenceViewInSelectedRow.getRight();
  
  				sel = makeColumn(mStackFromBottom ? columnEnd : columnStart, oldRight + horizontalSpacing, true);
  				referenceView = mReferenceView;
  
  				adjustForRightFadingEdge(referenceView, leftSelectionPixel, rightSelectionPixel);
  			} else if (rowDelta < 0) {
  				/*
  				 * Case 2: Scrolling left.
  				 */
  				final int oldTop = mReferenceViewInSelectedRow == null ?
  						0 : mReferenceViewInSelectedRow.getLeft();
  
  				sel = makeColumn(mStackFromBottom ? columnEnd : columnStart, oldTop - horizontalSpacing, false);
  				referenceView = mReferenceView;
  
  				adjustForLeftFadingEdge(referenceView, leftSelectionPixel, rightSelectionPixel);
  			} else {
  				/*
  				 * Keep selection where it was
  				 */
  				final int oldTop = mReferenceViewInSelectedRow == null ?
  						0 : mReferenceViewInSelectedRow.getLeft();
  
  				sel = makeColumn(mStackFromBottom ? columnEnd : columnStart, oldTop, true);
  				referenceView = mReferenceView;
  			}
  
  			if (!mStackFromBottom) {
  				fillLeft(columnStart - numRows, referenceView.getLeft() - horizontalSpacing);
  				adjustViewsLeftOrRight();
  				fillRight(columnStart + numRows, referenceView.getRight() + horizontalSpacing);
  			} else {
  				fillRight(columnStart + numRows, referenceView.getRight() + horizontalSpacing);
  				adjustViewsLeftOrRight();
  				fillLeft(columnStart - 1, referenceView.getLeft() - horizontalSpacing);
  			}
  
  			return sel;
  		}
  
  
  		@Override
  		protected void layoutChildren() {
  			final int childrenLeft = mListPadding.left;
  			final int childrenRight = getRight() - getLeft() - mListPadding.right;
  
  			int childCount = getChildCount();
  			int index;
  			int delta = 0;
  
  			View sel;
  			View oldSel = null;
  			View oldFirst = null;
  			View newSel = null;
  
  			// Remember stuff we will need down below
  			switch (mLayoutMode) {
  			case LAYOUT_SET_SELECTION:
  				index = mNextSelectedPosition - mFirstPosition;
  				if (index >= 0 && index < childCount) {
  					newSel = getChildAt(index);
  				}
  				break;
  			case LAYOUT_FORCE_TOP:
  			case LAYOUT_FORCE_BOTTOM:
  			case LAYOUT_SPECIFIC:
  			case LAYOUT_SYNC:
  				break;
  			case LAYOUT_MOVE_SELECTION:
  				if (mNextSelectedPosition >= 0) {
  					delta = mNextSelectedPosition - mSelectedPosition;
  				}
  				break;
  			default:
  				// Remember the previously selected view
  				index = mSelectedPosition - mFirstPosition;
  				if (index >= 0 && index < childCount) {
  					oldSel = getChildAt(index);
  				}
  
  				// Remember the previous first child
  				oldFirst = getChildAt(0);
  			}
  
  			boolean dataChanged = mDataChanged;
  			if (dataChanged) {
  				handleDataChanged();
  			}
  
  			// Handle the empty set by removing all views that are visible
  			// and calling it a day
  			if (mItemCount == 0) {
  				resetList();
  				invokeOnItemScrollListener();
  				return;
  			}
  
  			setSelectedPositionInt(mNextSelectedPosition);
  
  			// Pull all children into the RecycleBin.
  			// These views will be reused if possible
  			final int firstPosition = mFirstPosition;
  			final RecycleBin recycleBin = mRecycler;
  
  			if (dataChanged) {
  				for (int i = 0; i < childCount; i++) {
  					recycleBin.addScrapView(getChildAt(i));
  				}
  			} else {
  				recycleBin.fillActiveViews(childCount, firstPosition);
  			}
  
  			// Clear out old views
  			//removeAllViewsInLayout();
  			detachAllViewsFromParent();
  
  			switch (mLayoutMode) {
  			case LAYOUT_SET_SELECTION:
  				if (newSel != null) {
  					sel = fillFromSelection(newSel.getLeft(), childrenLeft, childrenRight);
  				} else {
  					sel = fillSelection(childrenLeft, childrenRight);
  				}
  				break;
  			case LAYOUT_FORCE_TOP:
  				mFirstPosition = 0;
  				sel = fillFromTop(childrenLeft);
  				adjustViewsLeftOrRight();
  				break;
  			case LAYOUT_FORCE_BOTTOM:
  				sel = fillRight(mItemCount - 1, childrenRight);
  				adjustViewsLeftOrRight();
  				break;
  			case LAYOUT_SPECIFIC:
  				sel = fillSpecific(mSelectedPosition, mSpecificTop);
  				break;
  			case LAYOUT_SYNC:
  				sel = fillSpecific(mSyncPosition, mSpecificTop);
  				break;
  			case LAYOUT_MOVE_SELECTION:
  				// Move the selection relative to its old position
  				sel = moveSelection(delta, childrenLeft, childrenRight);
  				break;
  			default:
  				if (childCount == 0) {
  					if (!mStackFromBottom) {
  						setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
  								INVALID_POSITION : 0);
  						sel = fillFromTop(childrenLeft);
  					} else {
  						final int last = mItemCount - 1;
  						setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
  								INVALID_POSITION : last);
  						sel = fillFromBottom(last, childrenRight);
  					}
  				} else {
  					if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
  						sel = fillSpecific(mSelectedPosition, oldSel == null ?
  								childrenLeft : oldSel.getLeft());
  					} else if (mFirstPosition < mItemCount)  {
  						sel = fillSpecific(mFirstPosition, oldFirst == null ?
  								childrenLeft : oldFirst.getLeft());
  					} else {
  						sel = fillSpecific(0, childrenLeft);
  					}
  				}
  				break;
  			}
  
  			// Flush any cached views that did not get reused above
  			recycleBin.scrapActiveViews();
  
  			if (sel != null) {
  				positionSelector(sel);
  				mSelectedTop = sel.getLeft();
  			} else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
  				View child = getChildAt(mMotionPosition - mFirstPosition);
  				if (child != null) positionSelector(child);
  			} else {
  				mSelectedTop = 0;
  				mSelectorRect.setEmpty();
  			}
  
  			mLayoutMode = LAYOUT_NORMAL;
  			mDataChanged = false;
  			mNeedSync = false;
  			setNextSelectedPositionInt(mSelectedPosition);
  
  			updateScrollIndicators();
  
  			if (mItemCount > 0) {
  				checkSelectionChanged();
  			}
  
  			invokeOnItemScrollListener();
  		}
  
  
  
  		@Override
  		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  			int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  			int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  			int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  			int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  			if (DEBUG) Log.i(TAG, "horizontal onMeasure heightMode: " + heightMode);
  			if (heightMode == MeasureSpec.UNSPECIFIED) {
  				if (mRowHeight > 0) {
  					heightSize = mRowHeight + mListPadding.top + mListPadding.bottom;
  				} else {
  					heightSize = mListPadding.top + mListPadding.bottom;
  				}
  				heightSize += getHorizontalScrollbarHeight();
  			}
  
  			int childHeight = heightSize - mListPadding.top - mListPadding.bottom;
  			determineRows(childHeight);
  
  			int childWidth = 0;
  
  			mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
  			final int count = mItemCount;
  			if (count > 0) {
  				final View child = obtainView(0, mIsScrap);
  
  				TwoWayAbsListView.LayoutParams p = (TwoWayAbsListView.LayoutParams)child.getLayoutParams();
  				if (p == null) {
  					p = new TwoWayAbsListView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
  							ViewGroup.LayoutParams.FILL_PARENT, 0);
  					child.setLayoutParams(p);
  				}
  				p.viewType = mAdapter.getItemViewType(0);
  				p.forceAdd = true;
  
  				int childHeightSpec = getChildMeasureSpec(
  						MeasureSpec.makeMeasureSpec(mRowHeight, MeasureSpec.UNSPECIFIED), 0, p.height);
  				int childWidthSpec = getChildMeasureSpec(
  						MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY), 0, p.width);
  				child.measure(childWidthSpec, childHeightSpec);
  
  				childWidth = child.getMeasuredWidth();
  
  				if (mRecycler.shouldRecycleViewType(p.viewType)) {
  					mRecycler.addScrapView(child);
  				}
  			}
  
  			if (widthMode == MeasureSpec.UNSPECIFIED) {
  				widthSize = mListPadding.left + mListPadding.right + childWidth +
  				getHorizontalFadingEdgeLength() * 2;
  			}
  
  			if (widthMode == MeasureSpec.AT_MOST) {
  				int ourSize =  mListPadding.left + mListPadding.right;
  
  				final int numRows = mNumRows;
  				for (int i = 0; i < count; i += numRows) {
  					ourSize += childWidth;
  					if (i + numRows < count) {
  						ourSize += mHorizontalSpacing;
  					}
  					if (ourSize >= widthSize) {
  						ourSize = widthSize;
  						break;
  					}
  				}
  				widthSize = ourSize;
  			}
  
  			setMeasuredDimension(widthSize, heightSize);
  			mWidthMeasureSpec = widthMeasureSpec;
  			if (DEBUG) Log.i(TAG, "Horizontal onMeasure widthSize: " + widthSize + " heightSize: " + heightSize);
  		}
  
  
  		private void determineRows(int availableSpace) {
  			final int requestedVerticalSpacing = mRequestedVerticalSpacing;
  			final int stretchMode = mStretchMode;
  			final int requestedRowHeight = mRequestedRowHeight;
  			mHorizontalSpacing = mRequestedHorizontalSpacing;
  
  			if (mRequestedNumRows == AUTO_FIT) {
  				if (requestedRowHeight > 0) {
  					// Client told us to pick the number of rows
  					mNumRows = (availableSpace + mRequestedVerticalSpacing) /
  					(requestedRowHeight + mRequestedVerticalSpacing);
  				} else {
  					// Just make up a number if we don't have enough info
  					mNumRows = 2;
  				}
  			} else {
  				// We picked the rows
  				mNumRows = mRequestedNumRows;
  			}
  
  			if (mNumRows <= 0) {
  				mNumRows = 1;
  			}
  
  			if (stretchMode == NO_STRETCH) {
  				// Nobody stretches
  				mRowHeight = mRequestedRowHeight;
  				mVerticalSpacing = mRequestedVerticalSpacing;
  
  			} else {
  				int spaceLeftOver;
  				switch (stretchMode) {
  				case STRETCH_COLUMN_WIDTH:
  					spaceLeftOver = availableSpace - (mNumRows * requestedRowHeight) -
  					((mNumRows - 1) * requestedVerticalSpacing);
  					// Stretch the rows
  					mRowHeight = requestedRowHeight + spaceLeftOver / mNumRows;
  					mVerticalSpacing = requestedVerticalSpacing;
  					break;
  
  				case STRETCH_SPACING:
  					spaceLeftOver = availableSpace - (mNumRows * requestedRowHeight) -
  					((mNumRows - 1) * requestedVerticalSpacing);
  					// Stretch the spacing between rows
  					mRowHeight = requestedRowHeight;
  					if (mNumRows > 1) {
  						mVerticalSpacing = requestedVerticalSpacing +
  						spaceLeftOver / (mNumRows - 1);
  					} else {
  						mVerticalSpacing = requestedVerticalSpacing + spaceLeftOver;
  					}
  					break;
  
  				case STRETCH_SPACING_UNIFORM:
  					// Stretch the spacing between rows
  					spaceLeftOver = availableSpace - (mNumRows * requestedRowHeight) -
  					((mNumRows + 1) * requestedVerticalSpacing);
  					mRowHeight = requestedRowHeight;
  					if (mNumRows > 1) {
  						mVerticalSpacing = requestedVerticalSpacing + spaceLeftOver / (mNumRows + 1);
  					} else {
  						mVerticalSpacing = ((requestedVerticalSpacing * 2) + spaceLeftOver) / 2;
  					}
  					break;
  				}
  			}
  			if (DEBUG) Log.i(TAG, "determineRows() mRowHeight: " + mRowHeight + " mVerticalSpacing: " + mVerticalSpacing + " mStretchMode: " + mStretchMode);
  		}
  
  		/**
  		 * Fills the grid based on positioning the new selection at a specific
  		 * location. The selection may be moved so that it does not intersect the
  		 * faded edges. The grid is then filled upwards and downwards from there.
  		 *
  		 * @param selectedLeft Where the selected item should be
  		 * @param childrenLeft Where to start drawing children
  		 * @param childrenRight Last pixel where children can be drawn
  		 * @return The view that currently has selection
  		 */
  		private View fillFromSelection(int selectedLeft, int childrenLeft, int childrenRight) {
  			if (DEBUG) Log.i(TAG, "fillFromSelection() selectedLeft: " + selectedLeft + " childrenLeft: " + childrenLeft + CHILDREN_RIGHT + childrenRight + M_FIRST_POSITION + mFirstPosition);
  			final int fadingEdgeLength = getHorizontalFadingEdgeLength();
  			final int selectedPosition = mSelectedPosition;
  			final int numRows = mNumRows;
  			final int horizontalSpacing = mHorizontalSpacing;
  
  			int columnStart;
  			int columnEnd = -1;
  
  			if (!mStackFromBottom) {
  				columnStart = selectedPosition - (selectedPosition % numRows);
  			} else {
  				int invertedSelection = mItemCount - 1 - selectedPosition;
  
  				columnEnd = mItemCount - 1 - (invertedSelection - (invertedSelection % numRows));
  				columnStart = Math.max(0, columnEnd - numRows + 1);
  			}
  
  			View sel;
  			View referenceView;
  
  			int leftSelectionPixel = getLeftSelectionPixel(childrenLeft, fadingEdgeLength, columnStart);
  			int rightSelectionPixel = getRightSelectionPixel(childrenRight, fadingEdgeLength,
  					numRows, columnStart);
  
  			sel = makeColumn(mStackFromBottom ? columnEnd : columnStart, selectedLeft, true);
  			// Possibly changed again in fillLeft if we add rows above this one.
  			mFirstPosition = columnStart;
  
  			referenceView = mReferenceView;
  			adjustForLeftFadingEdge(referenceView, leftSelectionPixel, rightSelectionPixel);
  			adjustForRightFadingEdge(referenceView, leftSelectionPixel, rightSelectionPixel);
  
  			if (!mStackFromBottom) {
  				fillLeft(columnStart - numRows, referenceView.getLeft() - horizontalSpacing);
  				adjustViewsLeftOrRight();
  				fillRight(columnStart + numRows, referenceView.getRight() + horizontalSpacing);
  			} else {
  				fillRight(columnEnd + numRows, referenceView.getRight() + horizontalSpacing);
  				adjustViewsLeftOrRight();
  				fillLeft(columnStart - 1, referenceView.getLeft() - horizontalSpacing);
  			}
  
  
  			return sel;
  		}
  
  		/**
  		 * Calculate the right-most pixel we can draw the selection into
  		 *
  		 * @param childrenRight Right pixel were children can be drawn
  		 * @param fadingEdgeLength Length of the fading edge in pixels, if present
  		 * @param numColumns Number of columns in the grid
  		 * @param rowStart The start of the row that will contain the selection
  		 * @return The right-most pixel we can draw the selection into
  		 */
  		private int getRightSelectionPixel(int childrenRight, int fadingEdgeLength,
  				int numColumns, int rowStart) {
  			// Last pixel we can draw the selection into
  			int rightSelectionPixel = childrenRight;
  			if (rowStart + numColumns - 1 < mItemCount - 1) {
  				rightSelectionPixel -= fadingEdgeLength;
  			}
  			return rightSelectionPixel;
  		}
  
  		/**
  		 * Calculate the left-most pixel we can draw the selection into
  		 *
  		 * @param childrenLeft Left pixel were children can be drawn
  		 * @param fadingEdgeLength Length of the fading edge in pixels, if present
  		 * @param rowStart The start of the row that will contain the selection
  		 * @return The left-most pixel we can draw the selection into
  		 */
  		private int getLeftSelectionPixel(int childrenLeft, int fadingEdgeLength, int rowStart) {
  			// first pixel we can draw the selection into
  			int leftSelectionPixel = childrenLeft;
  			if (rowStart > 0) {
  				leftSelectionPixel += fadingEdgeLength;
  			}
  			return leftSelectionPixel;
  		}
  
  
  
  		/**
  		 * Move all views left so the selected row does not interesect the right
  		 * fading edge (if necessary).
  		 *
  		 * @param childInSelectedRow A child in the row that contains the selection
  		 * @param leftSelectionPixel The leftmost pixel we can draw the selection into
  		 * @param rightSelectionPixel The rightmost pixel we can draw the
  		 *        selection into
  		 */
  		private void adjustForRightFadingEdge(View childInSelectedRow,
  				int leftSelectionPixel, int rightSelectionPixel) {
  			// Some of the newly selected item extends below the bottom of the
  			// list
  			if (childInSelectedRow.getRight() > rightSelectionPixel) {
  
  				// Find space available to the left the selection into which we can
  				// scroll upwards
  				int spaceToLeft = childInSelectedRow.getLeft() - leftSelectionPixel;
  
  				// Find space required to bring the right of the selected item
  				// fully into view
  				int spaceToRight = childInSelectedRow.getRight() - rightSelectionPixel;
  				int offset = Math.min(spaceToLeft, spaceToRight);
  
  				// Now offset the selected item to get it into view
  				offsetChildrenLeftAndRight(-offset);
  			}
  		}
  
  		/**
  		 * Move all views right so the selected row does not interesect the left
  		 * fading edge (if necessary).
  		 *
  		 * @param childInSelectedRow A child in the row that contains the selection
  		 * @param leftSelectionPixel The leftmost pixel we can draw the selection into
  		 * @param rightSelectionPixel The rightmost pixel we can draw the
  		 *        selection into
  		 */
  		private void adjustForLeftFadingEdge(View childInSelectedRow,
  				int leftSelectionPixel, int rightSelectionPixel) {
  			// Some of the newly selected item extends above the top of the list
  			if (childInSelectedRow.getLeft() < leftSelectionPixel) {
  				// Find space required to bring the top of the selected item
  				// fully into view
  				int spaceToLeft = leftSelectionPixel - childInSelectedRow.getLeft();
  
  				// Find space available below the selection into which we can
  				// scroll downwards
  				int spaceToRight = rightSelectionPixel - childInSelectedRow.getRight();
  				int offset = Math.min(spaceToLeft, spaceToRight);
  
  				// Now offset the selected item to get it into view
  				offsetChildrenLeftAndRight(offset);
  			}
  		}
  
  
  		/**
  		 * Make sure views are touching the top or bottom edge, as appropriate for
  		 * our gravity
  		 */
  		private void adjustViewsLeftOrRight() {
  			final int childCount = getChildCount();
  
  			if (childCount > 0) {
  				int delta;
  				View child;
  
  				if (!mStackFromBottom) {
  					// Uh-oh -- we came up short. Slide all views left to make them
  					// align with the left
  					child = getChildAt(0);
  					delta = child.getLeft() - mListPadding.left;
  					if (mFirstPosition != 0) {
  						// It's OK to have some space to left the first item if it is
  						// part of the horizontal spacing
  						delta -= mHorizontalSpacing;
  					}
  					if (delta < 0) {
  						// We only are looking to see if we are too right, not too left
  						delta = 0;
  					}
  				} else {
  					// we are too left, slide all views right to align with right
  					child = getChildAt(childCount - 1);
  					delta = child.getRight() - (getWidth() - mListPadding.right);
  
  					if (mFirstPosition + childCount < mItemCount) {
  						// It's OK to have some space to right of the last item if it is
  						// part of the horizontal spacing
  						delta += mHorizontalSpacing;
  					}
  
  					if (delta > 0) {
  						// We only are looking to see if we are too left, not too right
  						delta = 0;
  					}
  				}
  
  				if (delta != 0) {
  					offsetChildrenLeftAndRight(-delta);
  				}
  			}
  		}
  
  
  		/**
  		 * Add a view as a child and make sure it is measured (if necessary) and
  		 * positioned properly.
  		 *
  		 * @param child The view to add
  		 * @param position The position of the view
  		 * @param x The x position relative to which this view will be positioned
  		 * @param flow if true, align left edge to x. If false, align right edge
  		 *        to x.
  		 * @param childrenTop Top edge where children should be positioned
  		 * @param selected Is this position selected?
  		 * @param recycled Has this view been pulled from the recycle bin? If so it
  		 *        does not need to be remeasured.
  		 * @param where Where to add the item in the list
  		 *
  		 */
  		private void setupChild(View child, int position, int x, boolean flow, int childrenTop,
  				boolean selected, boolean recycled, int where) {
  			boolean isSelected = selected && shouldShowSelector();
  			final boolean updateChildSelected = isSelected != child.isSelected();
  			final int mode = mTouchMode;
  			final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
  			mMotionPosition == position;
  			final boolean updateChildPressed = isPressed != child.isPressed();
  
  			boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
  
  			// Respect layout params that are already in the view. Otherwise make
  			// some up...
  			TwoWayAbsListView.LayoutParams p = (TwoWayAbsListView.LayoutParams)child.getLayoutParams();
  			if (p == null) {
  				p = new TwoWayAbsListView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
  						ViewGroup.LayoutParams.FILL_PARENT, 0);
  			}
  			p.viewType = mAdapter.getItemViewType(position);
  
  			if (recycled && !p.forceAdd) {
  				attachViewToParent(child, where, p);
  			} else {
  				p.forceAdd = false;
  				addViewInLayout(child, where, p, true);
  			}
  
  			if (updateChildSelected) {
  				child.setSelected(isSelected);
  				if (isSelected) {
  					requestFocus();
  				}
  			}
  
  			if (updateChildPressed) {
  				child.setPressed(isPressed);
  			}
  
  			if (needToMeasure) {
  				int childWidthSpec = ViewGroup.getChildMeasureSpec(
  						MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.width);
  
  				int childHeightSpec = ViewGroup.getChildMeasureSpec(
  						MeasureSpec.makeMeasureSpec(mRowHeight, MeasureSpec.EXACTLY), 0, p.height);
  				child.measure(childWidthSpec, childHeightSpec);
  			} else {
  				cleanupLayoutState(child);
  			}
  
  			final int w = child.getMeasuredWidth();
  			final int h = child.getMeasuredHeight();
  
  			final int childLeft = flow ? x : x - w;
  			int childTop;
  
  			switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
  			case Gravity.TOP:
  				childTop = childrenTop;
  				break;
  			case Gravity.CENTER_HORIZONTAL:
  				childTop = childrenTop + ((mRowHeight - h) / 2);
  				break;
  			case Gravity.RIGHT:
  				childTop = childrenTop + mRowHeight - h;
  				break;
  			default:
  				childTop = childrenTop;
  				break;
  			}
  
  			if (needToMeasure) {
  				final int childRight = childLeft + w;
  				final int childBottom = childTop + h;
  				child.layout(childLeft, childTop, childRight, childBottom);
  			} else {
  				child.offsetLeftAndRight(childLeft - child.getLeft());
  				child.offsetTopAndBottom(childTop - child.getTop());
  			}
  
  			if (mCachingStarted) {
  				child.setDrawingCacheEnabled(true);
  			}
  		}
  
  		private void pinToLeft(int childrenLeft) {
  			if (mFirstPosition == 0) {
  				final int left = getChildAt(0).getLeft();
  				final int offset = childrenLeft - left;
  				if (offset < 0) {
  					offsetChildrenLeftAndRight(offset);
  				}
  			}
  		}
  
  		private void pinToRight(int childrenRight) {
  			final int count = getChildCount();
  			if (mFirstPosition + count == mItemCount) {
  				final int right = getChildAt(count - 1).getRight();
  				final int offset = childrenRight - right;
  				if (offset > 0) {
  					offsetChildrenLeftAndRight(offset);
  				}
  			}
  		}
  
  		/**
  		 * Makes the item at the supplied position selected.
  		 *
  		 * @param position the position of the new selection
  		 */
  		@Override
  		protected void setSelectionInt(int position) {
  			int previousSelectedPosition = mNextSelectedPosition;
  
  			setNextSelectedPositionInt(position);
  			TwoWayGridView.this.layoutChildren();
  
  			final int next = mStackFromBottom ? mItemCount - 1  - mNextSelectedPosition :
  				mNextSelectedPosition;
  			final int previous = mStackFromBottom ? mItemCount - 1
  					- previousSelectedPosition : previousSelectedPosition;
  
  			final int nextColumn = next / mNumRows;
  			final int previousColumn = previous / mNumRows;
  
  			if (nextColumn != previousColumn) {
  				//awakenScrollBars();
  			}
  
  		}
  
  		/**
  		 * Scrolls to the next or previous item, horizontally or vertically.
  		 *
  		 * @param direction either {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
  		 *        {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
  		 *
  		 * @return whether selection was moved
  		 */
  		@Override
  		protected boolean arrowScroll(int direction) {
  			final int selectedPosition = mSelectedPosition;
  			final int numRows = mNumRows;
  
  			int startOfColumnPos;
  			int endOfColumnPos;
  
  			boolean moved = false;
  
  			if (!mStackFromBottom) {
  				startOfColumnPos = (selectedPosition / numRows) * numRows;
  				endOfColumnPos = Math.min(startOfColumnPos + numRows - 1, mItemCount - 1);
  			} else {
  				final int invertedSelection = mItemCount - 1 - selectedPosition;
  				endOfColumnPos = mItemCount - 1 - (invertedSelection / numRows) * numRows;
  				startOfColumnPos = Math.max(0, endOfColumnPos - numRows + 1);
  			}
  
  			switch (direction) {
  			case FOCUS_LEFT:
  				if (startOfColumnPos > 0) {
  					mLayoutMode = LAYOUT_MOVE_SELECTION;
  					setSelectionInt(Math.max(0, selectedPosition - numRows));
  					moved = true;
  				}
  				break;
  			case FOCUS_RIGHT:
  				if (startOfColumnPos < mItemCount - 1) {
  					mLayoutMode = LAYOUT_MOVE_SELECTION;
  					setSelectionInt(Math.min(selectedPosition + numRows, mItemCount - 1));
  					moved = true;
  				}
  				break;
  			case FOCUS_UP:
  				if (selectedPosition > startOfColumnPos) {
  					mLayoutMode = LAYOUT_MOVE_SELECTION;
  					setSelectionInt(Math.max(0, selectedPosition - 1));
  					moved = true;
  				}
  				break;
  			case FOCUS_DOWN:
  				if (selectedPosition < endOfColumnPos) {
  					mLayoutMode = LAYOUT_MOVE_SELECTION;
  					setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
  					moved = true;
  				}
  				break;
  			}
  
  			if (moved) {
  				playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
  				invokeOnItemScrollListener();
  			}
  
  			if (moved) {
  				//awakenScrollBars();
  			}
  
  			return moved;
  		}
  
  		/**
  		 * Is childIndex a candidate for next focus given the direction the focus
  		 * change is coming from?
  		 * @param childIndex The index to check.
  		 * @param direction The direction, one of
  		 *        {FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}
  		 * @return Whether childIndex is a candidate.
  		 */
  		@Override
  		protected boolean isCandidateSelection(int childIndex, int direction) {
  			final int count = getChildCount();
  			final int invertedIndex = count - 1 - childIndex;
  			final int numRows = mNumRows;
  
  			int columnStart;
  			int columnEnd;
  
  			if (!mStackFromBottom) {
  				columnStart = childIndex - (childIndex % numRows);
  				columnEnd = Math.max(columnStart + numRows - 1, count);
  			} else {
  				columnEnd = count - 1 - (invertedIndex - (invertedIndex % numRows));
  				columnStart = Math.max(0, columnEnd - numRows + 1);
  			}
  
  			switch (direction) {
  			case View.FOCUS_RIGHT:
  				// coming from left, selection is only valid if it is on left
  				// edge
  				return childIndex == columnStart;
  			case View.FOCUS_DOWN:
  				// coming from top; only valid if in top row
  				return columnStart == 0;
  			case View.FOCUS_LEFT:
  				// coming from right, must be on right edge
  				return childIndex == columnStart;
  			case View.FOCUS_UP:
  				// coming from bottom, need to be in last row
  				return columnStart == count - 1;
  			case View.FOCUS_FORWARD:
  				// coming from top-left, need to be first in top row
  				return childIndex == columnStart && columnStart == 0;
  			case View.FOCUS_BACKWARD:
  				// coming from bottom-right, need to be last in bottom row
  				return childIndex == columnEnd && columnEnd == count - 1;
  			default:
  				throw new IllegalArgumentException("direction must be one of "
  						+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
  						+ "FOCUS_FORWARD, FOCUS_BACKWARD}.");
  
  			}
  		}
  
  	}
  
  }
  
  