content: /*
   * Copyright (C) 2007 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.widget;
  
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.UnsupportedAppUsage;
  import android.content.Context;
  import android.content.Intent;
  import android.content.res.TypedArray;
  import android.graphics.Rect;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.Trace;
  import android.util.AttributeSet;
  import android.util.MathUtils;
  import android.view.Gravity;
  import android.view.KeyEvent;
  import android.view.SoundEffectConstants;
  import android.view.View;
  import android.view.ViewDebug;
  import android.view.ViewGroup;
  import android.view.ViewHierarchyEncoder;
  import android.view.ViewRootImpl;
  import android.view.accessibility.AccessibilityNodeInfo;
  import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
  import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
  import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
  import android.view.accessibility.AccessibilityNodeProvider;
  import android.view.animation.GridLayoutAnimationController;
  import android.view.inspector.InspectableProperty;
  import android.widget.RemoteViews.RemoteView;
  
  import com.android.internal.R;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  
  
  /**
   * A view that shows items in two-dimensional scrolling grid. The items in the
   * grid come from the {@link ListAdapter} associated with this view.
   *
   * <p>See the <a href="{@docRoot}guide/topics/ui/layout/gridview.html">Grid
   * View</a> guide.</p>
   *
   * @attr ref android.R.styleable#GridView_horizontalSpacing
   * @attr ref android.R.styleable#GridView_verticalSpacing
   * @attr ref android.R.styleable#GridView_stretchMode
   * @attr ref android.R.styleable#GridView_columnWidth
   * @attr ref android.R.styleable#GridView_numColumns
   * @attr ref android.R.styleable#GridView_gravity
   */
  @RemoteView
  public class GridView extends AbsListView {
      /** @hide */
      @IntDef(prefix = { "NO_STRETCH", "STRETCH_" }, value = {
              NO_STRETCH,
              STRETCH_SPACING,
              STRETCH_COLUMN_WIDTH,
              STRETCH_SPACING_UNIFORM
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface StretchMode {}
  
      /**
       * Disables stretching.
       *
       * @see #setStretchMode(int)
       */
      public static final int NO_STRETCH = 0;
      /**
       * Stretches the spacing between columns.
       *
       * @see #setStretchMode(int)
       */
      public static final int STRETCH_SPACING = 1;
      /**
       * Stretches columns.
       *
       * @see #setStretchMode(int)
       */
      public static final int STRETCH_COLUMN_WIDTH = 2;
      /**
       * Stretches the spacing between columns. The spacing is uniform.
       *
       * @see #setStretchMode(int)
       */
      public static final int STRETCH_SPACING_UNIFORM = 3;
  
      /**
       * Creates as many columns as can fit on screen.
       *
       * @see #setNumColumns(int)
       */
      public static final int AUTO_FIT = -1;
  
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521080)
      private int mNumColumns = AUTO_FIT;
  
      @UnsupportedAppUsage
      private int mHorizontalSpacing = 0;
      @UnsupportedAppUsage
      private int mRequestedHorizontalSpacing;
      @UnsupportedAppUsage
      private int mVerticalSpacing = 0;
      private int mStretchMode = STRETCH_COLUMN_WIDTH;
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521079)
      private int mColumnWidth;
      @UnsupportedAppUsage
      private int mRequestedColumnWidth;
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769395)
      private int mRequestedNumColumns;
  
      private View mReferenceView = null;
      private View mReferenceViewInSelectedRow = null;
  
      private int mGravity = Gravity.START;
  
      private final Rect mTempRect = new Rect();
  
      public GridView(Context context) {
          this(context, null);
      }
  
      public GridView(Context context, AttributeSet attrs) {
          this(context, attrs, R.attr.gridViewStyle);
      }
  
      public GridView(Context context, AttributeSet attrs, int defStyleAttr) {
          this(context, attrs, defStyleAttr, 0);
      }
  
      public GridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
  
          final TypedArray a = context.obtainStyledAttributes(
                  attrs, R.styleable.GridView, defStyleAttr, defStyleRes);
          saveAttributeDataForStyleable(context, R.styleable.GridView,
                  attrs, a, defStyleAttr, defStyleRes);
  
          int hSpacing = a.getDimensionPixelOffset(
                  R.styleable.GridView_horizontalSpacing, 0);
          setHorizontalSpacing(hSpacing);
  
          int vSpacing = a.getDimensionPixelOffset(
                  R.styleable.GridView_verticalSpacing, 0);
          setVerticalSpacing(vSpacing);
  
          int index = a.getInt(R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH);
          if (index >= 0) {
              setStretchMode(index);
          }
  
          int columnWidth = a.getDimensionPixelOffset(R.styleable.GridView_columnWidth, -1);
          if (columnWidth > 0) {
              setColumnWidth(columnWidth);
          }
  
          int numColumns = a.getInt(R.styleable.GridView_numColumns, 1);
          setNumColumns(numColumns);
  
          index = a.getInt(R.styleable.GridView_gravity, -1);
          if (index >= 0) {
              setGravity(index);
          }
  
          a.recycle();
      }
  
      @Override
      public ListAdapter getAdapter() {
          return mAdapter;
      }
  
      /**
       * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
       * through the specified intent.
       * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
       */
      @android.view.RemotableViewMethod(asyncImpl="setRemoteViewsAdapterAsync")
      public void setRemoteViewsAdapter(Intent intent) {
          super.setRemoteViewsAdapter(intent);
      }
  
      /**
       * Sets the data behind this GridView.
       *
       * @param adapter the adapter providing the grid's data
       */
      @Override
      public void setAdapter(ListAdapter adapter) {
          if (mAdapter != null && mDataSetObserver != null) {
              mAdapter.unregisterDataSetObserver(mDataSetObserver);
          }
  
          resetList();
          mRecycler.clear();
          mAdapter = adapter;
  
          mOldSelectedPosition = INVALID_POSITION;
          mOldSelectedRowId = INVALID_ROW_ID;
  
          // AbsListView#setAdapter will update choice mode states.
          super.setAdapter(adapter);
  
          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) {
          final int numColumns = mNumColumns;
          final int verticalSpacing = mVerticalSpacing;
  
          final int count = getChildCount();
  
          if (down) {
              int paddingTop = 0;
              if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                  paddingTop = getListPaddingTop();
              }
              final int startOffset = count > 0 ?
                      getChildAt(count - 1).getBottom() + verticalSpacing : paddingTop;
              int position = mFirstPosition + count;
              if (mStackFromBottom) {
                  position += numColumns - 1;
              }
              fillDown(position, startOffset);
              correctTooHigh(numColumns, verticalSpacing, getChildCount());
          } else {
              int paddingBottom = 0;
              if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                  paddingBottom = getListPaddingBottom();
              }
              final int startOffset = count > 0 ?
                      getChildAt(0).getTop() - verticalSpacing : getHeight() - paddingBottom;
              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.
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      private View fillDown(int pos, int nextTop) {
          View selectedView = null;
  
          int end = (mBottom - mTop);
          if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
              end -= 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;
          }
  
          setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
          return selectedView;
      }
  
      private View makeRow(int startPos, int y, boolean flow) {
          final int columnWidth = mColumnWidth;
          final int horizontalSpacing = mHorizontalSpacing;
  
          final boolean isLayoutRtl = isLayoutRtl();
  
          int last;
          int nextLeft;
  
          if (isLayoutRtl) {
              nextLeft = getWidth() - mListPadding.right - columnWidth -
                      ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
          } else {
              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) {
                  final int deltaLeft = (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing);
                  nextLeft += (isLayoutRtl ? -1 : +1) * deltaLeft;
              }
          }
  
          View selectedView = null;
  
          final boolean hasFocus = shouldShowSelector();
          final boolean inClick = touchModeDrawsInPressedState();
          final int selectedPosition = mSelectedPosition;
  
          View child = null;
          final int nextChildDir = isLayoutRtl ? -1 : +1;
          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 += nextChildDir * columnWidth;
              if (pos < last - 1) {
                  nextLeft += nextChildDir * 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
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      private View fillUp(int pos, int nextBottom) {
          View selectedView = null;
  
          int end = 0;
          if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
              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);
          }
  
          setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 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) {
          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) {
          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) {
          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;
      }
  
      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);
              }
          }
      }
  
      @Override
      int findMotionRow(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;
      }
  
      /**
       * 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) {
          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) {
          // 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 = (mBottom - mTop) - 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 (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 = (mBottom - mTop) - 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) {
          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);
          }
      }
  
      /**
       * Smoothly scroll to the specified adapter position. The view will
       * scroll such that the indicated position is displayed.
       * @param position Scroll to this adapter position.
       */
      @android.view.RemotableViewMethod
      public void smoothScrollToPosition(int position) {
          super.smoothScrollToPosition(position);
      }
  
      /**
       * Smoothly scroll to the specified adapter position offset. The view will
       * scroll such that the indicated position is displayed.
       * @param offset The amount to offset from the adapter position to scroll to.
       */
      @android.view.RemotableViewMethod
      public void smoothScrollByOffset(int offset) {
          super.smoothScrollByOffset(offset);
      }
  
      /**
       * 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) {
          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;
      }
  
      @UnsupportedAppUsage
      private boolean determineColumns(int availableSpace) {
          final int requestedHorizontalSpacing = mRequestedHorizontalSpacing;
          final int stretchMode = mStretchMode;
          final int requestedColumnWidth = mRequestedColumnWidth;
          boolean didNotInitiallyFit = false;
  
          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;
          }
  
          switch (stretchMode) {
              case NO_STRETCH:
                  // Nobody stretches
                  mColumnWidth = requestedColumnWidth;
                  mHorizontalSpacing = requestedHorizontalSpacing;
                  break;
  
              default:
                  int spaceLeftOver = availableSpace - (mNumColumns * requestedColumnWidth)
                          - ((mNumColumns - 1) * requestedHorizontalSpacing);
  
                  if (spaceLeftOver < 0) {
                      didNotInitiallyFit = true;
                  }
  
                  switch (stretchMode) {
                      case STRETCH_COLUMN_WIDTH:
                          // Stretch the columns
                          mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
                          mHorizontalSpacing = requestedHorizontalSpacing;
                          break;
  
                      case STRETCH_SPACING:
                          // Stretch the spacing between columns
                          mColumnWidth = requestedColumnWidth;
                          if (mNumColumns > 1) {
                              mHorizontalSpacing = requestedHorizontalSpacing
                                      + spaceLeftOver / (mNumColumns - 1);
                          } else {
                              mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
                          }
                          break;
  
                      case STRETCH_SPACING_UNIFORM:
                          // Stretch the spacing between columns
                          mColumnWidth = requestedColumnWidth;
                          if (mNumColumns > 1) {
                              mHorizontalSpacing = requestedHorizontalSpacing
                                      + spaceLeftOver / (mNumColumns + 1);
                          } else {
                              mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
                          }
                          break;
                  }
  
                  break;
          }
          return didNotInitiallyFit;
      }
  
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          // Sets up mListPadding
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  
          int widthMode = MeasureSpec.getMode(widthMeasureSpec);
          int heightMode = MeasureSpec.getMode(heightMeasureSpec);
          int widthSize = MeasureSpec.getSize(widthMeasureSpec);
          int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  
          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;
          boolean didNotInitiallyFit = determineColumns(childWidth);
  
          int childHeight = 0;
          int childState = 0;
  
          mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
          final int count = mItemCount;
          if (count > 0) {
              final View child = obtainView(0, mIsScrap);
  
              AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
              if (p == null) {
                  p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
                  child.setLayoutParams(p);
              }
              p.viewType = mAdapter.getItemViewType(0);
              p.isEnabled = mAdapter.isEnabled(0);
              p.forceAdd = true;
  
              int childHeightSpec = getChildMeasureSpec(
                      MeasureSpec.makeSafeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
                              MeasureSpec.UNSPECIFIED), 0, p.height);
              int childWidthSpec = getChildMeasureSpec(
                      MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
              child.measure(childWidthSpec, childHeightSpec);
  
              childHeight = child.getMeasuredHeight();
              childState = combineMeasuredStates(childState, child.getMeasuredState());
  
              if (mRecycler.shouldRecycleViewType(p.viewType)) {
                  mRecycler.addScrapView(child, -1);
              }
          }
  
          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;
          }
  
          if (widthMode == MeasureSpec.AT_MOST && mRequestedNumColumns != AUTO_FIT) {
              int ourSize = (mRequestedNumColumns*mColumnWidth)
                      + ((mRequestedNumColumns-1)*mHorizontalSpacing)
                      + mListPadding.left + mListPadding.right;
              if (ourSize > widthSize || didNotInitiallyFit) {
                  widthSize |= MEASURED_STATE_TOO_SMALL;
              }
          }
  
          setMeasuredDimension(widthSize, heightSize);
          mWidthMeasureSpec = widthMeasureSpec;
      }
  
      @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;
              }
  
              final int childrenTop = mListPadding.top;
              final int childrenBottom = mBottom - mTop - 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);
  
              AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
              View accessibilityFocusLayoutRestoreView = null;
              int accessibilityFocusPosition = INVALID_POSITION;
  
              // Remember which child, if any, had accessibility focus. This must
              // occur before recycling any views, since that will clear
              // accessibility focus.
              final ViewRootImpl viewRootImpl = getViewRootImpl();
              if (viewRootImpl != null) {
                  final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
                  if (focusHost != null) {
                      final View focusChild = getAccessibilityFocusedChild(focusHost);
                      if (focusChild != null) {
                          if (!dataChanged || focusChild.hasTransientState()
                                  || mAdapterHasStableIds) {
                              // The views won't be changing, so try to maintain
                              // focus on the current host and virtual view.
                              accessibilityFocusLayoutRestoreView = focusHost;
                              accessibilityFocusLayoutRestoreNode = viewRootImpl
                                      .getAccessibilityFocusedVirtualView();
                          }
  
                          // Try to maintain focus at the same position.
                          accessibilityFocusPosition = getPositionForView(focusChild);
                      }
                  }
              }
  
              // 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), firstPosition+i);
                  }
              } else {
                  recycleBin.fillActiveViews(childCount, firstPosition);
              }
  
              // Clear out old views
              detachAllViewsFromParent();
              recycleBin.removeSkippedScrap();
  
              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(INVALID_POSITION, sel);
                 mSelectedTop = sel.getTop();
              } else {
                  final boolean inTouchMode = mTouchMode > TOUCH_MODE_DOWN
                          && mTouchMode < TOUCH_MODE_SCROLL;
                  if (inTouchMode) {
                      // If the user's finger is down, select the motion position.
                      final View child = getChildAt(mMotionPosition - mFirstPosition);
                      if (child != null) {
                          positionSelector(mMotionPosition, child);
                      }
                  } else if (mSelectedPosition != INVALID_POSITION) {
                      // If we had previously positioned the selector somewhere,
                      // put it back there. It might not match up with the data,
                      // but it's transitioning out so it's not a big deal.
                      final View child = getChildAt(mSelectorPosition - mFirstPosition);
                      if (child != null) {
                          positionSelector(mSelectorPosition, child);
                      }
                  } else {
                      // Otherwise, clear selection.
                      mSelectedTop = 0;
                      mSelectorRect.setEmpty();
                  }
              }
  
              // Attempt to restore accessibility focus, if necessary.
              if (viewRootImpl != null) {
                  final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
                  if (newAccessibilityFocusedView == null) {
                      if (accessibilityFocusLayoutRestoreView != null
                              && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
                          final AccessibilityNodeProvider provider =
                                  accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
                          if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
                              final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
                                      accessibilityFocusLayoutRestoreNode.getSourceNodeId());
                              provider.performAction(virtualViewId,
                                      AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
                          } else {
                              accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
                          }
                      } else if (accessibilityFocusPosition != INVALID_POSITION) {
                          // Bound the position within the visible children.
                          final int position = MathUtils.constrain(
                                  accessibilityFocusPosition - mFirstPosition, 0,
                                  getChildCount() - 1);
                          final View restoreView = getChildAt(position);
                          if (restoreView != null) {
                              restoreView.requestAccessibilityFocus();
                          }
                      }
                  }
              }
  
              mLayoutMode = LAYOUT_NORMAL;
              mDataChanged = false;
              if (mPositionScrollAfterLayout != null) {
                  post(mPositionScrollAfterLayout);
                  mPositionScrollAfterLayout = null;
              }
              mNeedSync = false;
              setNextSelectedPositionInt(mSelectedPosition);
  
              updateScrollIndicators();
  
              if (mItemCount > 0) {
                  checkSelectionChanged();
              }
  
              invokeOnItemScrollListener();
          } finally {
              if (!blockLayoutRequests) {
                  mBlockLayoutRequests = false;
              }
          }
      }
  
  
      /**
       * Obtains the view and adds 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 {@code true} to align top edge to y, {@code false} to align
       *             bottom edge to y
       * @param childrenLeft left edge where children should be positioned
       * @param selected {@code true} if the position is selected, {@code false}
       *                 otherwise
       * @param where position at which to add new item in the list
       * @return View that was added
       */
      private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
              boolean selected, int where) {
          if (!mDataChanged) {
              // Try to use an existing view for this position
              final View activeView = mRecycler.getActiveView(position);
              if (activeView != null) {
                  // Found it -- we're using an existing child
                  // This just needs to be positioned
                  setupChild(activeView, position, y, flow, childrenLeft, selected, true, where);
                  return activeView;
              }
          }
  
          // Make a new view for this position, or convert an unused view if
          // possible.
          final View child = obtainView(position, mIsScrap);
  
          // This needs to be positioned and measured.
          setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
  
          return child;
      }
  
      /**
       * Adds 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 this child
       * @param y the y position relative to which this view will be positioned
       * @param flowDown {@code true} to align top edge to y, {@code false} to
       *                 align bottom edge to y
       * @param childrenLeft left edge where children should be positioned
       * @param selected {@code true} if the position is selected, {@code false}
       *                 otherwise
       * @param isAttachedToWindow {@code true} if the view is already attached
       *                           to the window, e.g. whether it was reused, or
       *                           {@code false} otherwise
       * @param where position at which to add new item in the list
       *
       */
      private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
              boolean selected, boolean isAttachedToWindow, int where) {
          Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupGridItem");
  
          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();
          final boolean needToMeasure = !isAttachedToWindow || updateChildSelected
                  || child.isLayoutRequested();
  
          // Respect layout params that are already in the view. Otherwise make
          // some up...
          AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
          if (p == null) {
              p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
          }
          p.viewType = mAdapter.getItemViewType(position);
          p.isEnabled = mAdapter.isEnabled(position);
  
          // Set up view state before attaching the view, since we may need to
          // rely on the jumpDrawablesToCurrentState() call that occurs as part
          // of view attachment.
          if (updateChildSelected) {
              child.setSelected(isSelected);
              if (isSelected) {
                  requestFocus();
              }
          }
  
          if (updateChildPressed) {
              child.setPressed(isPressed);
          }
  
          if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
              if (child instanceof Checkable) {
                  ((Checkable) child).setChecked(mCheckStates.get(position));
              } else if (getContext().getApplicationInfo().targetSdkVersion
                      >= android.os.Build.VERSION_CODES.HONEYCOMB) {
                  child.setActivated(mCheckStates.get(position));
              }
          }
  
          if (isAttachedToWindow && !p.forceAdd) {
              attachViewToParent(child, where, p);
  
              // If the view isn't attached, or if it's attached but for a different
              // position, then jump the drawables.
              if (!isAttachedToWindow
                      || (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
                              != position) {
                  child.jumpDrawablesToCurrentState();
              }
          } else {
              p.forceAdd = false;
              addViewInLayout(child, where, p, true);
          }
  
          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 = flowDown ? y : y - h;
  
          final int layoutDirection = getLayoutDirection();
          final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
          switch (absoluteGravity & 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.isDrawingCacheEnabled()) {
              child.setDrawingCacheEnabled(true);
          }
  
          Trace.traceEnd(Trace.TRACE_TAG_VIEW);
      }
  
      /**
       * 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;
          if (mPositionScroller != null) {
              mPositionScroller.stop();
          }
          requestLayout();
      }
  
      /**
       * Makes the item at the supplied position selected.
       *
       * @param position the position of the new selection
       */
      @Override
      void setSelectionInt(int position) {
          int previousSelectedPosition = mNextSelectedPosition;
  
          if (mPositionScroller != null) {
              mPositionScroller.stop();
          }
  
          setNextSelectedPositionInt(position);
          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();
          }
  
      }
  
      @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 (KeyEvent.isConfirmKey(keyCode)
                  && event.hasNoModifiers() && action != KeyEvent.ACTION_UP) {
              handled = resurrectSelectionIfNeeded();
              if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) {
                  keyPressed();
                  handled = true;
              }
          }
  
          if (!handled && action != KeyEvent.ACTION_UP) {
              switch (keyCode) {
                  case KeyEvent.KEYCODE_DPAD_LEFT:
                      if (event.hasNoModifiers()) {
                          handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_LEFT);
                      }
                      break;
  
                  case KeyEvent.KEYCODE_DPAD_RIGHT:
                      if (event.hasNoModifiers()) {
                          handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_RIGHT);
                      }
                      break;
  
                  case KeyEvent.KEYCODE_DPAD_UP:
                      if (event.hasNoModifiers()) {
                          handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
                      } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                          handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
                      }
                      break;
  
                  case KeyEvent.KEYCODE_DPAD_DOWN:
                      if (event.hasNoModifiers()) {
                          handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
                      } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                          handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
                      }
                      break;
  
                  case KeyEvent.KEYCODE_PAGE_UP:
                      if (event.hasNoModifiers()) {
                          handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
                      } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                          handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
                      }
                      break;
  
                  case KeyEvent.KEYCODE_PAGE_DOWN:
                      if (event.hasNoModifiers()) {
                          handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
                      } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                          handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
                      }
                      break;
  
                  case KeyEvent.KEYCODE_MOVE_HOME:
                      if (event.hasNoModifiers()) {
                          handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
                      }
                      break;
  
                  case KeyEvent.KEYCODE_MOVE_END:
                      if (event.hasNoModifiers()) {
                          handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
                      }
                      break;
  
                  case KeyEvent.KEYCODE_TAB:
                      // TODO: Sometimes it is useful to be able to TAB through the items in
                      //     a GridView sequentially.  Unfortunately this can create an
                      //     asymmetry in TAB navigation order unless the list selection
                      //     always reverts to the top or bottom when receiving TAB focus from
                      //     another widget.
                      if (event.hasNoModifiers()) {
                          handled = resurrectSelectionIfNeeded()
                                  || sequenceScroll(FOCUS_FORWARD);
                      } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                          handled = resurrectSelectionIfNeeded()
                                  || sequenceScroll(FOCUS_BACKWARD);
                      }
                      break;
              }
          }
  
          if (handled) {
              return true;
          }
  
          if (sendToTextFilter(keyCode, count, event)) {
              return true;
          }
  
          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;
  
          if (direction == FOCUS_UP) {
              nextPage = Math.max(0, mSelectedPosition - getChildCount());
          } else if (direction == FOCUS_DOWN) {
              nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount());
          }
  
          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;
      }
  
      /**
       * 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
       */
      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;
          }
  
          final boolean isLayoutRtl = isLayoutRtl();
          if (selectedPosition > startOfRowPos && ((direction == FOCUS_LEFT && !isLayoutRtl) ||
                  (direction == FOCUS_RIGHT && isLayoutRtl))) {
              mLayoutMode = LAYOUT_MOVE_SELECTION;
              setSelectionInt(Math.max(0, selectedPosition - 1));
              moved = true;
          } else if (selectedPosition < endOfRowPos && ((direction == FOCUS_LEFT && isLayoutRtl) ||
                  (direction == FOCUS_RIGHT && !isLayoutRtl))) {
              mLayoutMode = LAYOUT_MOVE_SELECTION;
              setSelectionInt(Math.min(selectedPosition + 1, mItemCount - 1));
              moved = true;
          }
  
          if (moved) {
              playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
              invokeOnItemScrollListener();
          }
  
          if (moved) {
              awakenScrollBars();
          }
  
          return moved;
      }
  
      /**
       * Goes to the next or previous item according to the order set by the
       * adapter.
       */
      @UnsupportedAppUsage
      boolean sequenceScroll(int direction) {
          int selectedPosition = mSelectedPosition;
          int numColumns = mNumColumns;
          int count = mItemCount;
  
          int startOfRow;
          int endOfRow;
          if (!mStackFromBottom) {
              startOfRow = (selectedPosition / numColumns) * numColumns;
              endOfRow = Math.min(startOfRow + numColumns - 1, count - 1);
          } else {
              int invertedSelection = count - 1 - selectedPosition;
              endOfRow = count - 1 - (invertedSelection / numColumns) * numColumns;
              startOfRow = Math.max(0, endOfRow - numColumns + 1);
          }
  
          boolean moved = false;
          boolean showScroll = false;
          switch (direction) {
              case FOCUS_FORWARD:
                  if (selectedPosition < count - 1) {
                      // Move to the next item.
                      mLayoutMode = LAYOUT_MOVE_SELECTION;
                      setSelectionInt(selectedPosition + 1);
                      moved = true;
                      // Show the scrollbar only if changing rows.
                      showScroll = selectedPosition == endOfRow;
                  }
                  break;
  
              case FOCUS_BACKWARD:
                  if (selectedPosition > 0) {
                      // Move to the previous item.
                      mLayoutMode = LAYOUT_MOVE_SELECTION;
                      setSelectionInt(selectedPosition - 1);
                      moved = true;
                      // Show the scrollbar only if changing rows.
                      showScroll = selectedPosition == startOfRow;
                  }
                  break;
          }
  
          if (moved) {
              playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
              invokeOnItemScrollListener();
          }
  
          if (showScroll) {
              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(mScrollX, mScrollY);
  
              // 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 (!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();
          }
      }
  
      /**
       * 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, FOCUS_FORWARD, FOCUS_BACKWARD}
       * @return Whether childIndex is a candidate.
       */
      private 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.min(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}.");
          }
      }
  
      /**
       * Set the gravity for this grid. Gravity 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#GridView_gravity
       */
      public void setGravity(int gravity) {
          if (mGravity != gravity) {
              mGravity = gravity;
              requestLayoutIfNecessary();
          }
      }
  
      /**
       * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
       *
       * @return the gravity that will be applied to this grid's children
       *
       * @attr ref android.R.styleable#GridView_gravity
       */
      @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
      public int getGravity() {
          return mGravity;
      }
  
      /**
       * 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#GridView_horizontalSpacing
       */
      public void setHorizontalSpacing(int horizontalSpacing) {
          if (horizontalSpacing != mRequestedHorizontalSpacing) {
              mRequestedHorizontalSpacing = horizontalSpacing;
              requestLayoutIfNecessary();
          }
      }
  
      /**
       * Returns the amount of horizontal spacing currently used between each item in the grid.
       *
       * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
       * has been called but layout is not yet complete, this method may return a stale value.
       * To get the horizontal spacing that was explicitly requested use
       * {@link #getRequestedHorizontalSpacing()}.</p>
       *
       * @return Current horizontal spacing between each item in pixels
       *
       * @see #setHorizontalSpacing(int)
       * @see #getRequestedHorizontalSpacing()
       *
       * @attr ref android.R.styleable#GridView_horizontalSpacing
       */
      @InspectableProperty
      public int getHorizontalSpacing() {
          return mHorizontalSpacing;
      }
  
      /**
       * Returns the requested amount of horizontal spacing between each item in the grid.
       *
       * <p>The value returned may have been supplied during inflation as part of a style,
       * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
       * If layout is not yet complete or if GridView calculated a different horizontal spacing
       * from what was requested, this may return a different value from
       * {@link #getHorizontalSpacing()}.</p>
       *
       * @return The currently requested horizontal spacing between items, in pixels
       *
       * @see #setHorizontalSpacing(int)
       * @see #getHorizontalSpacing()
       *
       * @attr ref android.R.styleable#GridView_horizontalSpacing
       */
      public int getRequestedHorizontalSpacing() {
          return mRequestedHorizontalSpacing;
      }
  
      /**
       * 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.
       *
       * @see #getVerticalSpacing()
       *
       * @attr ref android.R.styleable#GridView_verticalSpacing
       */
      public void setVerticalSpacing(int verticalSpacing) {
          if (verticalSpacing != mVerticalSpacing) {
              mVerticalSpacing = verticalSpacing;
              requestLayoutIfNecessary();
          }
      }
  
      /**
       * Returns the amount of vertical spacing between each item in the grid.
       *
       * @return The vertical spacing between items in pixels
       *
       * @see #setVerticalSpacing(int)
       *
       * @attr ref android.R.styleable#GridView_verticalSpacing
       */
      @InspectableProperty
      public int getVerticalSpacing() {
          return mVerticalSpacing;
      }
  
      /**
       * 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#GridView_stretchMode
       */
      public void setStretchMode(@StretchMode int stretchMode) {
          if (stretchMode != mStretchMode) {
              mStretchMode = stretchMode;
              requestLayoutIfNecessary();
          }
      }
  
      @StretchMode
      @InspectableProperty(enumMapping = {
              @InspectableProperty.EnumEntry(value = NO_STRETCH, name = "none"),
              @InspectableProperty.EnumEntry(value = STRETCH_SPACING, name = "spacingWidth"),
              @InspectableProperty.EnumEntry(
                      value = STRETCH_SPACING_UNIFORM, name = "spacingWidthUniform"),
              @InspectableProperty.EnumEntry(value = STRETCH_COLUMN_WIDTH, name = "columnWidth"),
      })
      public int getStretchMode() {
          return mStretchMode;
      }
  
      /**
       * Set the width of columns in the grid.
       *
       * @param columnWidth The column width, in pixels.
       *
       * @attr ref android.R.styleable#GridView_columnWidth
       */
      public void setColumnWidth(int columnWidth) {
          if (columnWidth != mRequestedColumnWidth) {
              mRequestedColumnWidth = columnWidth;
              requestLayoutIfNecessary();
          }
      }
  
      /**
       * Return the width of a column in the grid.
       *
       * <p>This may not be valid yet if a layout is pending.</p>
       *
       * @return The column width in pixels
       *
       * @see #setColumnWidth(int)
       * @see #getRequestedColumnWidth()
       *
       * @attr ref android.R.styleable#GridView_columnWidth
       */
      @InspectableProperty
      public int getColumnWidth() {
          return mColumnWidth;
      }
  
      /**
       * Return the requested width of a column in the grid.
       *
       * <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
       * to retrieve the current real width of a column.</p>
       *
       * @return The requested column width in pixels
       *
       * @see #setColumnWidth(int)
       * @see #getColumnWidth()
       *
       * @attr ref android.R.styleable#GridView_columnWidth
       */
      public int getRequestedColumnWidth() {
          return mRequestedColumnWidth;
      }
  
      /**
       * Set the number of columns in the grid
       *
       * @param numColumns The desired number of columns.
       *
       * @attr ref android.R.styleable#GridView_numColumns
       */
      public void setNumColumns(int numColumns) {
          if (numColumns != mRequestedNumColumns) {
              mRequestedNumColumns = numColumns;
              requestLayoutIfNecessary();
          }
      }
  
      /**
       * Get the number of columns in the grid.
       * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
       *
       * @attr ref android.R.styleable#GridView_numColumns
       *
       * @see #setNumColumns(int)
       */
      @ViewDebug.ExportedProperty
      @InspectableProperty
      public int getNumColumns() {
          return mNumColumns;
      }
  
      /**
       * 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);
              }
          }
      }
  
      @Override
      protected int computeVerticalScrollExtent() {
          final int count = getChildCount();
          if (count > 0) {
              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) {
              final View view = getChildAt(0);
              final int top = view.getTop();
              int height = view.getHeight();
              if (height > 0) {
                  final int numColumns = mNumColumns;
                  final int rowCount = (mItemCount + numColumns - 1) / numColumns;
                  // In case of stackFromBottom the calculation of whichRow needs
                  // to take into account that counting from the top the first row
                  // might not be entirely filled.
                  final int oddItemsOnFirstRow = isStackFromBottom() ? ((rowCount * numColumns) -
                          mItemCount) : 0;
                  final int whichRow = (mFirstPosition + oddItemsOnFirstRow) / numColumns;
                  return Math.max(whichRow * 100 - (top * 100) / height +
                          (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
              }
          }
          return 0;
      }
  
      @Override
      protected int computeVerticalScrollRange() {
          // TODO: Account for vertical spacing too
          final int numColumns = mNumColumns;
          final int rowCount = (mItemCount + numColumns - 1) / numColumns;
          int result = Math.max(rowCount * 100, 0);
          if (mScrollY != 0) {
              // Compensate for overscroll
              result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
          }
          return result;
      }
  
      @Override
      public CharSequence getAccessibilityClassName() {
          return GridView.class.getName();
      }
  
      /** @hide */
      @Override
      public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
          super.onInitializeAccessibilityNodeInfoInternal(info);
  
          final int columnsCount = getNumColumns();
          final int rowsCount = getCount() / columnsCount;
          final int selectionMode = getSelectionModeForAccessibility();
          final CollectionInfo collectionInfo = CollectionInfo.obtain(
                  rowsCount, columnsCount, false, selectionMode);
          info.setCollectionInfo(collectionInfo);
  
          if (columnsCount > 0 || rowsCount > 0) {
              info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
          }
      }
  
      /** @hide */
      @Override
      public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
          if (super.performAccessibilityActionInternal(action, arguments)) {
              return true;
          }
  
          switch (action) {
              case R.id.accessibilityActionScrollToPosition: {
                  // GridView only supports scrolling in one direction, so we can
                  // ignore the column argument.
                  final int numColumns = getNumColumns();
                  final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
                  final int position = Math.min(row * numColumns, getCount() - 1);
                  if (row >= 0) {
                      // The accessibility service gets data asynchronously, so
                      // we'll be a little lenient by clamping the last position.
                      smoothScrollToPosition(position);
                      return true;
                  }
              } break;
          }
  
          return false;
      }
  
      @Override
      public void onInitializeAccessibilityNodeInfoForItem(
              View view, int position, AccessibilityNodeInfo info) {
          super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
  
          final int count = getCount();
          final int columnsCount = getNumColumns();
          final int rowsCount = count / columnsCount;
  
          final int row;
          final int column;
          if (!mStackFromBottom) {
              column = position % columnsCount;
              row = position / columnsCount;
          } else {
              final int invertedIndex = count - 1 - position;
  
              column = columnsCount - 1 - (invertedIndex % columnsCount);
              row = rowsCount - 1 - invertedIndex / columnsCount;
          }
  
          final LayoutParams lp = (LayoutParams) view.getLayoutParams();
          final boolean isHeading = lp != null && lp.viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
          final boolean isSelected = isItemChecked(position);
          final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
                  row, 1, column, 1, isHeading, isSelected);
          info.setCollectionItemInfo(itemInfo);
      }
  
      /** @hide */
      @Override
      protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
          super.encodeProperties(encoder);
          encoder.addProperty("numColumns", getNumColumns());
      }
  }
  