Introduction of GridLayout.

Change-Id: Ia2ec315e7d29dcc5aa13b080fc6ce00708e9c034
diff --git a/api/current.txt b/api/current.txt
index 2f5b9b4..fdef20f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -301,7 +301,9 @@
     field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
     field public static final int colorForeground = 16842800; // 0x1010030
     field public static final int colorForegroundInverse = 16843270; // 0x1010206
+    field public static final int columnCount = 16843633; // 0x1010371
     field public static final int columnDelay = 16843215; // 0x10101cf
+    field public static final int columnOrderPreserved = 16843634; // 0x1010372
     field public static final int columnWidth = 16843031; // 0x1010117
     field public static final int completionHint = 16843122; // 0x1010172
     field public static final int completionHintView = 16843123; // 0x1010173
@@ -575,6 +577,8 @@
     field public static final int layout_centerInParent = 16843151; // 0x101018f
     field public static final int layout_centerVertical = 16843153; // 0x1010191
     field public static final int layout_column = 16843084; // 0x101014c
+    field public static final int layout_columnSpan = 16843639; // 0x1010377
+    field public static final int layout_columnWeight = 16843640; // 0x1010378
     field public static final int layout_gravity = 16842931; // 0x10100b3
     field public static final int layout_height = 16842997; // 0x10100f5
     field public static final int layout_margin = 16842998; // 0x10100f6
@@ -582,6 +586,9 @@
     field public static final int layout_marginLeft = 16842999; // 0x10100f7
     field public static final int layout_marginRight = 16843001; // 0x10100f9
     field public static final int layout_marginTop = 16843000; // 0x10100f8
+    field public static final int layout_row = 16843636; // 0x1010374
+    field public static final int layout_rowSpan = 16843637; // 0x1010375
+    field public static final int layout_rowWeight = 16843638; // 0x1010376
     field public static final int layout_scale = 16843155; // 0x1010193
     field public static final int layout_span = 16843085; // 0x101014d
     field public static final int layout_toLeftOf = 16843138; // 0x1010182
@@ -751,9 +758,11 @@
     field public static final int rotation = 16843558; // 0x1010326
     field public static final int rotationX = 16843559; // 0x1010327
     field public static final int rotationY = 16843560; // 0x1010328
+    field public static final int rowCount = 16843631; // 0x101036f
     field public static final int rowDelay = 16843216; // 0x10101d0
     field public static final int rowEdgeFlags = 16843329; // 0x1010241
     field public static final int rowHeight = 16843058; // 0x1010132
+    field public static final int rowOrderPreserved = 16843632; // 0x1010370
     field public static final int saveEnabled = 16842983; // 0x10100e7
     field public static final int scaleGravity = 16843262; // 0x10101fe
     field public static final int scaleHeight = 16843261; // 0x10101fd
@@ -984,6 +993,7 @@
     field public static final int unfocusedMonthDateColor = 16843588; // 0x1010344
     field public static final int unselectedAlpha = 16843278; // 0x101020e
     field public static final int updatePeriodMillis = 16843344; // 0x1010250
+    field public static final int useDefaultMargins = 16843635; // 0x1010373
     field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
     field public static final int useLevel = 16843167; // 0x101019f
     field public static final int userVisible = 16843409; // 0x1010291
@@ -24167,6 +24177,67 @@
     ctor public Gallery.LayoutParams(android.view.ViewGroup.LayoutParams);
   }
 
+  public class GridLayout extends android.view.ViewGroup {
+    ctor public GridLayout(android.content.Context);
+    ctor public GridLayout(android.content.Context, android.util.AttributeSet, int);
+    ctor public GridLayout(android.content.Context, android.util.AttributeSet);
+    method public int getColumnCount();
+    method public int getOrientation();
+    method public int getRowCount();
+    method public boolean getUseDefaultMargins();
+    method public boolean isColumnOrderPreserved();
+    method public boolean isRowOrderPreserved();
+    method protected void onLayout(boolean, int, int, int, int);
+    method public void setColumnCount(int);
+    method public void setColumnOrderPreserved(boolean);
+    method public void setOrientation(int);
+    method public void setRowCount(int);
+    method public void setRowOrderPreserved(boolean);
+    method public void setUseDefaultMargins(boolean);
+    field public static final android.widget.GridLayout.Alignment BASELINE;
+    field public static final android.widget.GridLayout.Alignment BOTTOM;
+    field public static final android.widget.GridLayout.Alignment CENTER;
+    field public static final android.widget.GridLayout.Alignment FILL;
+    field public static final int HORIZONTAL = 0; // 0x0
+    field public static final android.widget.GridLayout.Alignment LEFT;
+    field public static final android.widget.GridLayout.Alignment RIGHT;
+    field public static final android.widget.GridLayout.Alignment TOP;
+    field public static final int VERTICAL = 1; // 0x1
+  }
+
+  public static abstract interface GridLayout.Alignment {
+    method public abstract int getAlignmentValue(android.view.View, int);
+    method public abstract int getSizeInCell(android.view.View, int, int);
+  }
+
+  public static class GridLayout.Group {
+    ctor public GridLayout.Group(android.widget.GridLayout.Interval, android.widget.GridLayout.Alignment);
+    ctor public GridLayout.Group(int, int, android.widget.GridLayout.Alignment);
+    ctor public GridLayout.Group(int, android.widget.GridLayout.Alignment);
+    field public final android.widget.GridLayout.Alignment alignment;
+    field public final android.widget.GridLayout.Interval span;
+  }
+
+  public static class GridLayout.Interval {
+    ctor public GridLayout.Interval(int, int);
+    field public final int max;
+    field public final int min;
+  }
+
+  public static class GridLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+    ctor public GridLayout.LayoutParams(android.widget.GridLayout.Group, android.widget.GridLayout.Group);
+    ctor public GridLayout.LayoutParams();
+    ctor public GridLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+    ctor public GridLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+    ctor public GridLayout.LayoutParams(android.widget.GridLayout.LayoutParams);
+    ctor public GridLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
+    method public void setGravity(int);
+    field public android.widget.GridLayout.Group columnGroup;
+    field public float columnWeight;
+    field public android.widget.GridLayout.Group rowGroup;
+    field public float rowWeight;
+  }
+
   public class GridView extends android.widget.AbsListView {
     ctor public GridView(android.content.Context);
     ctor public GridView(android.content.Context, android.util.AttributeSet);
@@ -25049,6 +25120,12 @@
     method public abstract void onScrollStarted();
   }
 
+  public final class Space extends android.view.View {
+    ctor public Space(android.content.Context, android.util.AttributeSet, int);
+    ctor public Space(android.content.Context, android.util.AttributeSet);
+    ctor public Space(android.content.Context);
+  }
+
   public class Spinner extends android.widget.AbsSpinner implements android.content.DialogInterface.OnClickListener {
     ctor public Spinner(android.content.Context);
     ctor public Spinner(android.content.Context, int);
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
new file mode 100644
index 0000000..4889101
--- /dev/null
+++ b/core/java/android/widget/GridLayout.java
@@ -0,0 +1,2036 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.internal.R.styleable;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * A layout that places its children in a rectangular <em>grid</em>.
+ * <p>
+ * The grid is composed of a set of infinitely thin lines that separate the
+ * viewing area into <em>cells</em>. Throughout the API, grid lines are referenced
+ * by grid <em>indices</em>. A grid that has <code>N</code> columns
+ * has <code>N + 1</code> grid indices that run from <code>0</code>
+ * through <code>N</code> inclusive. Regardless of how GridLayout is
+ * configured, grid index <code>0</code> is fixed to the leading edge of the
+ * container and grid index <code>N</code> is fixed to its trailing edge
+ * (after padding is taken into account).
+ *
+ * <h4>Row and Column Groups</h4>
+ *
+ * Children occupy one or more contiguous cells, as defined
+ * by their {@link GridLayout.LayoutParams#rowGroup rowGroup} and
+ * {@link GridLayout.LayoutParams#columnGroup columnGroup} layout parameters.
+ * Each group specifies the set of rows or columns that are to be
+ * occupied; and how children should be aligned within the resulting group of cells.
+ * Although cells do not normally overlap in a GridLayout, GridLayout does
+ * not prevent children being defined to occupy the same cell or group of cells.
+ * In this case however, there is no guarantee that children will not themselves
+ * overlap after the layout operation completes.
+ *
+ * <h4>Default Cell Assignment</h4>
+ *
+ * If no child specifies the row and column indices of the cell it
+ * wishes to occupy, GridLayout assigns cell locations automatically using its:
+ * {@link GridLayout#setOrientation(int) orientation},
+ * {@link GridLayout#setRowCount(int) rowCount} and
+ * {@link GridLayout#setColumnCount(int) columnCount} properties.
+ *
+ * <h4>Space</h4>
+ *
+ * Space between children may be specified either by using instances of the
+ * dedicated {@link Space} view or by setting the
+ *
+ * {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin},
+ * {@link ViewGroup.MarginLayoutParams#topMargin topMargin},
+ * {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and
+ * {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin}
+ *
+ * layout parameters. When the
+ * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
+ * property is set, default margins around children are automatically
+ * allocated based on the child's visual characteristics. Each of the
+ * margins so defined may be independently overridden by an assignment
+ * to the appropriate layout parameter.
+ *
+ * <h4>Excess Space Distribution</h4>
+ *
+ * Like {@link LinearLayout}, a child's ability to stretch is controlled
+ * using <em>weights</em>, which are specified using the
+ * {@link GridLayout.LayoutParams#rowWeight rowWeight} and
+ * {@link GridLayout.LayoutParams#columnWeight columnWeight} layout parameters.
+ * <p>
+ * <p>
+ * See {@link GridLayout.LayoutParams} for a full description of the
+ * layout parameters used by GridLayout.
+ *
+ * @attr ref android.R.styleable#GridLayout_orientation
+ * @attr ref android.R.styleable#GridLayout_rowCount
+ * @attr ref android.R.styleable#GridLayout_columnCount
+ * @attr ref android.R.styleable#GridLayout_useDefaultMargins
+ * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
+ * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+ */
+public class GridLayout extends ViewGroup {
+
+    // Public constants
+
+    /**
+     * The horizontal orientation.
+     */
+    public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+    /**
+     * The vertical orientation.
+     */
+    public static final int VERTICAL = LinearLayout.VERTICAL;
+
+    // Misc constants
+
+    private static final String TAG = GridLayout.class.getName();
+    private static final boolean DEBUG = false;
+    private static final int UNDEFINED = Integer.MIN_VALUE;
+    private static final Paint GRID_PAINT = new Paint();
+    private static final double GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;
+    private static final int MIN = 0;
+    private static final int PRF = 1;
+    private static final int MAX = 2;
+
+    // Defaults
+
+    private static final int DEFAULT_ORIENTATION = HORIZONTAL;
+    private static final int DEFAULT_COUNT = UNDEFINED;
+    private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
+    private static final boolean DEFAULT_ORDER_PRESERVED = false;
+
+    // TypedArray indices
+
+    private static final int ORIENTATION = styleable.GridLayout_orientation;
+    private static final int ROW_COUNT = styleable.GridLayout_rowCount;
+    private static final int COLUMN_COUNT = styleable.GridLayout_columnCount;
+    private static final int USE_DEFAULT_MARGINS = styleable.GridLayout_useDefaultMargins;
+    private static final int ROW_ORDER_PRESERVED = styleable.GridLayout_rowOrderPreserved;
+    private static final int COLUMN_ORDER_PRESERVED = styleable.GridLayout_columnOrderPreserved;
+
+    // Instance variables
+
+    private final Axis mHorizontalAxis = new Axis(true);
+    private final Axis mVerticalAxis = new Axis(false);
+    private boolean mLayoutParamsValid = false;
+    private int mOrientation = DEFAULT_ORIENTATION;
+    private boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
+    private int mDefaultGravity = Gravity.NO_GRAVITY;
+    boolean maximizing = false;
+    boolean accommodateBothMinAndMax = false;
+
+    // Constructors
+
+    /**
+     * {@inheritDoc}
+     */
+    public GridLayout(Context context) {
+        super(context);
+        if (DEBUG) {
+            setWillNotDraw(false);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public GridLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        processAttributes(context, attrs);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public GridLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        processAttributes(context, attrs);
+    }
+
+    private void processAttributes(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout);
+        try {
+            setRowCount(a.getInteger(ROW_COUNT, DEFAULT_COUNT));
+            setColumnCount(a.getInteger(COLUMN_COUNT, DEFAULT_COUNT));
+            mOrientation = a.getInteger(ORIENTATION, DEFAULT_ORIENTATION);
+            mUseDefaultMargins = a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS);
+            setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
+            setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
+        } finally {
+            a.recycle();
+        }
+    }
+
+    // Implementation
+
+    /**
+     * Returns the current orientation.
+     *
+     * @return either {@link #HORIZONTAL} or {@link #VERTICAL}. The default
+     * is {@link #HORIZONTAL}.
+     *
+     * @see #setOrientation(int)
+     *
+     * @attr ref android.R.styleable#GridLayout_orientation
+     */
+    public int getOrientation() {
+        return mOrientation;
+    }
+
+    /**
+     * The orientation property does not affect layout. Orientation is used
+     * only to generate default row/column indices when they are not specified
+     * by a component's layout parameters.
+     *
+     * @param orientation the orientation, either {@link #HORIZONTAL} or {@link #VERTICAL}.
+     *
+     * @see #getOrientation()
+     *
+     * @attr ref android.R.styleable#GridLayout_orientation
+     */
+    public void setOrientation(int orientation) {
+        if (mOrientation != orientation) {
+            mOrientation = orientation;
+            requestLayout();
+        }
+    }
+
+    /**
+     * Returns the current number of rows. This is either the last value that was set
+     * with {@link #setRowCount(int)} or, if no such value was set, the maximum
+     * value of each the upper bounds defined in {@link LayoutParams#rowGroup}.
+     *
+     * @return the current number of rows
+     *
+     * @see #setRowCount(int)
+     * @see LayoutParams#rowGroup
+     *
+     * @attr ref android.R.styleable#GridLayout_rowCount
+     */
+    public int getRowCount() {
+        return mVerticalAxis.getCount();
+    }
+
+    /**
+     * The rowCount property does not affect layout. RowCount is used
+     * only to generate default row/column indices when they are not specified
+     * by a component's layout parameters.
+     *
+     * @param rowCount the number of rows.
+     *
+     * @see #getRowCount()
+     * @see LayoutParams#rowGroup
+     *
+     * @attr ref android.R.styleable#GridLayout_rowCount
+     */
+    public void setRowCount(int rowCount) {
+        mVerticalAxis.setCount(rowCount);
+    }
+
+    /**
+     * Returns the current number of columns. This is either the last value that was set
+     * with {@link #setColumnCount(int)} or, if no such value was set, the maximum
+     * value of each the upper bounds defined in {@link LayoutParams#columnGroup}.
+     *
+     * @return the current number of columns
+     *
+     * @see #setColumnCount(int)
+     * @see LayoutParams#columnGroup
+     *
+     * @attr ref android.R.styleable#GridLayout_columnCount
+     */
+    public int getColumnCount() {
+        return mHorizontalAxis.getCount();
+    }
+
+    /**
+     * The columnCount property does not affect layout. ColumnCount is used
+     * only to generate default column/column indices when they are not specified
+     * by a component's layout parameters.
+     *
+     * @param columnCount the number of columns.
+     *
+     * @see #getColumnCount()
+     * @see LayoutParams#columnGroup
+     *
+     * @attr ref android.R.styleable#GridLayout_columnCount
+     */
+    public void setColumnCount(int columnCount) {
+        mHorizontalAxis.setCount(columnCount);
+    }
+
+    /**
+     * Returns whether or not this GridLayout will allocate default margins when no
+     * corresponding layout parameters are defined.
+     *
+     * @return true if default margins should be allocated.
+     *
+     * @see #setUseDefaultMargins(boolean)
+     *
+     * @attr ref android.R.styleable#GridLayout_useDefaultMargins
+     */
+    public boolean getUseDefaultMargins() {
+        return mUseDefaultMargins;
+    }
+
+    /**
+     * When true, GridLayout allocates default margins around children
+     * based on the child's visual characteristics. Each of the
+     * margins so defined may be independently overridden by an assignment
+     * to the appropriate layout parameter.
+     * <p>
+     * When false, the default value of all margins is zero.
+     *
+     * @param useDefaultMargins use true to make GridLayout allocate default margins
+     *
+     * @see #getUseDefaultMargins()
+     *
+     * @see MarginLayoutParams#leftMargin
+     * @see MarginLayoutParams#topMargin
+     * @see MarginLayoutParams#rightMargin
+     * @see MarginLayoutParams#bottomMargin
+     *
+     * @attr ref android.R.styleable#GridLayout_useDefaultMargins
+     */
+    public void setUseDefaultMargins(boolean useDefaultMargins) {
+        mUseDefaultMargins = useDefaultMargins;
+    }
+
+    /**
+     * Returns whether or not row boundaries are ordered by their grid indices.
+     *
+     * @return true if row boundaries must appear in the order of their indices, false otherwise.
+     *         The default is false.
+     *
+     * @see #setRowOrderPreserved(boolean)
+     *
+     * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
+     */
+    public boolean isRowOrderPreserved() {
+        return mVerticalAxis.isOrderPreserved();
+    }
+
+    /**
+     * When this property is <code>false</code>, the default state, GridLayout
+     * is at liberty to choose an order that better suits the heights of its children.
+       <p>
+     * When this property is <code>true</code>, GridLayout is forced to place row boundaries
+     * (the {@link Interval#min min} and {@link Interval#max max} values of
+     * a {@link LayoutParams#rowGroup rowGroup}'s {@link Group#span span})
+     * so that they appear in ascending order in the view.
+     * <p>
+     * GridLayout implements this specification by creating ordering constraints between
+     * the variables that represent the locations of the row boundaries.
+     *
+     * When this property is <code>true</code>, constraints are added for each pair of consecutive
+     * indices: i.e. between row boundaries: <code>[0..1], [1..2], [3..4],...</code> etc.
+     *
+     * When the property is <code>false</code>, the ordering constraints are placed
+     * only between boundaries that separate opposing edges of the layout's children.
+     *
+     * @param rowOrderPreserved use true to force GridLayout to respect the order
+     *        of row boundaries.
+     *
+     * @see #isRowOrderPreserved()
+     *
+     * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
+     */
+    public void setRowOrderPreserved(boolean rowOrderPreserved) {
+        mVerticalAxis.setOrderPreserved(rowOrderPreserved);
+    }
+
+    /**
+     * Returns whether or not column boundaries are ordered by their grid indices.
+     *
+     * @return true if column boundaries must appear in the order of their indices, false otherwise.
+     *         The default is false.
+     *
+     * @see #setColumnOrderPreserved(boolean)
+     *
+     * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+     */
+    public boolean isColumnOrderPreserved() {
+        return mHorizontalAxis.isOrderPreserved();
+    }
+
+    /**
+     * When this property is <code>false</code>, the default state, GridLayout
+     * is at liberty to choose an order that better suits the widths of its children.
+       <p>
+     * When this property is <code>true</code>, GridLayout is forced to place column boundaries
+     * (the {@link Interval#min min} and {@link Interval#max max} values of
+     * a {@link LayoutParams#columnGroup columnGroup}'s {@link Group#span span})
+     * so that they appear in ascending order in the view.
+     * <p>
+     * GridLayout implements this specification by creating ordering constraints between
+     * the variables that represent the locations of the column boundaries.
+     *
+     * When this property is <code>true</code>, constraints are added for each pair of consecutive
+     * indices: i.e. between column boundaries: <code>[0..1], [1..2], [3..4],...</code> etc.
+     *
+     * When the property is <code>false</code>, the ordering constraints are placed
+     * only between boundaries that separate opposing edges of the layout's children.
+     *
+     * @param columnOrderPreserved use true to force GridLayout to respect the order
+     *        of column boundaries.
+     *
+     * @see #isColumnOrderPreserved()
+     *
+     * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+     */
+    public void setColumnOrderPreserved(boolean columnOrderPreserved) {
+        mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
+    }
+
+    private static int compare(int i, int j) {
+        return i < j ? -1 : i > j ? 1 : 0;
+    }
+
+    private static int sum(int[] a) {
+        int result = 0;
+        for (int i = 0, length = a.length; i < length; i++) {
+            result += a[i];
+        }
+        return result;
+    }
+
+    private int getDefaultMargin(View c, boolean leading, boolean horizontal) {
+        // In the absence of any other information, calculate a default gap such
+        // that, in a grid of identical components, the heights and the vertical
+        // gaps are in the proportion of the golden ratio.
+        // To effect this with equal margins at each edge, set each of the
+        // four margin values to half this amount.
+        c.measure(0, 0);
+        return (int) (c.getMeasuredHeight() / GOLDEN_RATIO / 2);
+    }
+
+    private int getDefaultMargin(View c, boolean isAtEdge, boolean leading, boolean horizontal) {
+        // todo remove 20 - use padding here?
+        return isAtEdge ? 20 : getDefaultMargin(c, leading, horizontal);
+    }
+
+    private int getDefaultMarginValue(View c, LayoutParams p, boolean leading, boolean horizontal) {
+        if (!mUseDefaultMargins) {
+            return 0;
+        }
+        Group group = horizontal ? p.columnGroup : p.rowGroup;
+        Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
+        Interval span = group.span;
+        boolean isAtEdge = leading ? span.min == 0 : span.max == axis.getCount();
+
+        return getDefaultMargin(c, isAtEdge, leading, horizontal);
+    }
+
+    private int getMargin(View view, boolean leading, boolean horizontal) {
+        LayoutParams lp = getLayoutParams(view);
+        int margin = horizontal ?
+                leading ? lp.leftMargin : lp.rightMargin :
+                leading ? lp.topMargin : lp.bottomMargin;
+        return margin == UNDEFINED ? getDefaultMarginValue(view, lp, leading, horizontal) : margin;
+    }
+
+    private static boolean isUndefined(Interval span) {
+        return span.min == UNDEFINED || span.max == UNDEFINED;
+    }
+
+    private void validateLayoutParams() {
+        // install default indices for cells if *none* are defined
+        if (mHorizontalAxis.maxIndex1() == UNDEFINED || mVerticalAxis.maxIndex1() == UNDEFINED) {
+            boolean horizontal = mOrientation == HORIZONTAL;
+            int count = horizontal ? mHorizontalAxis.count : mVerticalAxis.count;
+            if (count == UNDEFINED) {
+                count = Integer.MAX_VALUE;
+            }
+            int x = 0;
+            int y = 0;
+            int maxSize = 0;
+            for (int i = 0, size = getChildCount(); i < size; i++) {
+                LayoutParams lp = getLayoutParams1(getChildAt(i));
+
+                Interval hSpan = lp.columnGroup.span;
+                int cellWidth = hSpan.size();
+
+                Interval vSpan = lp.rowGroup.span;
+                int cellHeight = vSpan.size();
+
+                if (horizontal) {
+                    if (x + cellWidth > count) {
+                        x = 0;
+                        y += maxSize;
+                        maxSize = 0;
+                    }
+                } else {
+                    if (y + cellHeight > count) {
+                        y = 0;
+                        x += maxSize;
+                        maxSize = 0;
+                    }
+                }
+                lp.setHorizontalGroupSpan(new Interval(x, x + cellWidth));
+                lp.setVerticalGroupSpan(new Interval(y, y + cellHeight));
+
+                if (horizontal) {
+                    x = x + cellWidth;
+                } else {
+                    y = y + cellHeight;
+                }
+                maxSize = max(maxSize, horizontal ? cellHeight : cellWidth);
+            }
+        } else {
+            /*
+            At least one row and one column index have been defined.
+            Assume missing row/cols are in error and set them to zero so that
+            they will display top/left and the developer can add the right indices.
+            Without this UNDEFINED would cause ArrayIndexOutOfBoundsException.
+            */
+            for (int i = 0, size = getChildCount(); i < size; i++) {
+                LayoutParams lp = getLayoutParams1(getChildAt(i));
+                if (isUndefined(lp.columnGroup.span)) {
+                    lp.setHorizontalGroupSpan(LayoutParams.DEFAULT_SPAN);
+                }
+                if (isUndefined(lp.rowGroup.span)) {
+                    lp.setVerticalGroupSpan(LayoutParams.DEFAULT_SPAN);
+                }
+            }
+        }
+    }
+
+    private void invalidateStructure() {
+        mLayoutParamsValid = false;
+        mHorizontalAxis.invalidateStructure();
+        mVerticalAxis.invalidateStructure();
+
+        // This can end up being done twice. But better that than not at all.
+        invalidateValues();
+    }
+
+    private void invalidateValues() {
+        mHorizontalAxis.invalidateValues();
+        mVerticalAxis.invalidateValues();
+    }
+
+    private LayoutParams getLayoutParams1(View c) {
+        return (LayoutParams) c.getLayoutParams();
+    }
+
+    private LayoutParams getLayoutParams(View c) {
+        if (!mLayoutParamsValid) {
+            validateLayoutParams();
+            mLayoutParamsValid = true;
+        }
+        return getLayoutParams1(c);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams();
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs, mDefaultGravity);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    // Draw grid
+
+    private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
+        int dx = getPaddingLeft();
+        int dy = getPaddingTop();
+        graphics.drawLine(dx + x1, dy + y1, dx + x2, dy + y2, paint);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (DEBUG) {
+            int height = getHeight() - getPaddingTop() - getPaddingBottom();
+            int width = getWidth() - getPaddingLeft() - getPaddingRight();
+
+            int[] xs = mHorizontalAxis.locations;
+            for (int i = 0, length = xs.length; i < length; i++) {
+                int x = xs[i];
+                drawLine(canvas, x, 0, x, height - 1, GRID_PAINT);
+            }
+            int[] ys = mVerticalAxis.locations;
+            for (int i = 0, length = ys.length; i < length; i++) {
+                int y = ys[i];
+                drawLine(canvas, 0, y, width - 1, y, GRID_PAINT);
+            }
+        }
+    }
+
+    static {
+        GRID_PAINT.setColor(Color.argb(50, 255, 255, 255));
+    }
+
+    // Add/remove
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        super.addView(child, index, params);
+        invalidateStructure();
+    }
+
+    @Override
+    public void removeView(View view) {
+        super.removeView(view);
+        invalidateStructure();
+    }
+
+    @Override
+    public void removeViewInLayout(View view) {
+        super.removeViewInLayout(view);
+        invalidateStructure();
+    }
+
+    @Override
+    public void removeViewsInLayout(int start, int count) {
+        super.removeViewsInLayout(start, count);
+        invalidateStructure();
+    }
+
+    @Override
+    public void removeViewAt(int index) {
+        super.removeViewAt(index);
+        invalidateStructure();
+    }
+
+    // Measurement
+
+    @Override
+    protected void onMeasure(int widthSpec, int heightSpec) {
+        invalidateValues();
+        // int width = MeasureSpec.getSize(widthSpec);
+        // int widthMode = MeasureSpec.getMode(widthSpec);
+        // int height = MeasureSpec.getSize(heightSpec);
+        // int heightMode = MeasureSpec.getMode(heightSpec);
+
+        // todo - handle widthSpec and heightSpec properly
+
+        int computedWidth = getPaddingLeft() + mHorizontalAxis.getPref() + getPaddingRight();
+        int computedHeight = getPaddingTop() + mVerticalAxis.getPref() + getPaddingBottom();
+
+        setMeasuredDimension(
+                resolveSizeAndState(computedWidth, widthSpec, 0),
+                resolveSizeAndState(computedHeight, heightSpec, 0));
+    }
+
+    private int protect(int alignment) {
+        return alignment == UNDEFINED ? 0 : alignment;
+    }
+
+    private int getLocationIncludingMargin(Axis state, int index, boolean leading) {
+        int margin = leading ? state.leadingMargins[index] : -state.trailingMargins[index];
+        return state.locations[index] + margin;
+    }
+
+    private int getMeasurement(View c, boolean horizontal, int measurementType) {
+        LayoutParams lp = (LayoutParams) c.getLayoutParams();
+        // First check to see if the user has specified the size.
+        // If so, return the specified size.
+        int size = horizontal ? lp.width : lp.height;
+        if (size >= 0) {
+            return size;
+        }
+
+        // measureChild(c, 0, 0);
+        c.measure(0, 0);// todo work out correct order of events for measurement calls
+        int result = horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
+
+        float weight = horizontal ? lp.columnWeight : lp.rowWeight;
+        Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
+        if (weight != 0) {
+            return result + axis.prefSizeOfWeightedComponent;
+        }
+        return result;
+    }
+
+    // Layout container
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        invalidateValues();
+
+        int targetWidth = r - l;
+        int targetHeight = b - t;
+
+        int paddingLeft = getPaddingLeft();
+        int paddingTop = getPaddingTop();
+        int paddingRight = getPaddingRight();
+        int paddingBottom = getPaddingBottom();
+
+        mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
+        mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom);
+
+        for (int i = 0, size = getChildCount(); i < size; i++) {
+            View view = getChildAt(i);
+            LayoutParams constraints = getLayoutParams(view);
+            Interval hRange = constraints.columnGroup.span;
+            Interval vRange = constraints.rowGroup.span;
+
+            int x1 = getLocationIncludingMargin(mHorizontalAxis, hRange.min, true);
+            int y1 = getLocationIncludingMargin(mVerticalAxis, vRange.min, true);
+
+            int x2 = getLocationIncludingMargin(mHorizontalAxis, hRange.max, false);
+            int y2 = getLocationIncludingMargin(mVerticalAxis, vRange.max, false);
+
+            int cellWidth = x2 - x1;
+            int cellHeight = y2 - y1;
+
+            Bounds minMaxX = mHorizontalAxis.getGroupBounds().getValue(i);
+            Bounds minMaxY = mVerticalAxis.getGroupBounds().getValue(i);
+
+            int pWidth = getMeasurement(view, true, PRF);
+            int pHeight = getMeasurement(view, false, PRF);
+
+            Alignment hAlignment = constraints.columnGroup.alignment;
+            Alignment vAlignment = constraints.rowGroup.alignment;
+
+            int ddx = protect(hAlignment.getAlignmentValue(null, cellWidth - minMaxX.size()));
+            int ddy = protect(vAlignment.getAlignmentValue(null, cellHeight - minMaxY.size()));
+
+            int dx = ddx + -minMaxX.below - hAlignment.getAlignmentValue(view, pWidth);
+            int dy = ddy + -minMaxY.below - vAlignment.getAlignmentValue(view, pHeight);
+
+            int width = hAlignment.getSizeInCell(view, pWidth, cellWidth);
+            int height = vAlignment.getSizeInCell(view, pHeight, cellHeight);
+
+            int cx = paddingLeft + x1 + dx;
+            int cy = paddingTop + y1 + dy;
+            view.layout(cx, cy, cx + width, cy + height);
+        }
+    }
+
+    // Inner classes
+
+    private class Axis {
+        private static final int MIN_VALUE = -1000000;
+        private static final int MAX_VALUE = 1000000;
+
+        private static final int UNVISITED = 0;
+        private static final int PENDING = 1;
+        private static final int COMPLETE = 2;
+
+        public final boolean horizontal;
+
+        public int count = UNDEFINED;
+        public boolean countValid = false;
+        public boolean countWasExplicitySet = false;
+
+        PackedMap<Group, Bounds> groupBounds;
+        public boolean groupBoundsValid = false;
+
+        PackedMap<Interval, Int> spanSizes;
+        public boolean spanSizesValid = false;
+
+        public int[] locations;
+
+        public int[] leadingMargins;
+        public int[] trailingMargins;
+
+        public Arc[] arcs;
+        public boolean arcsValid = false;
+
+        private boolean mOrderPreserved = DEFAULT_ORDER_PRESERVED;
+
+        public int prefSizeOfWeightedComponent;
+
+        private Axis(boolean horizontal) {
+            this.horizontal = horizontal;
+        }
+
+        private int maxIndex(boolean internal) {
+            // note the number Integer.MIN_VALUE + 1 comes up in undefined cells
+            int count = -1;
+            for (int i = 0, size = getChildCount(); i < size; i++) {
+                LayoutParams params = internal ?
+                        getLayoutParams1(getChildAt(i)) :
+                        getLayoutParams(getChildAt(i));
+                Group g = horizontal ? params.columnGroup : params.rowGroup;
+                count = max(count, g.span.min);
+                count = max(count, g.span.max);
+            }
+            return count == -1 ? UNDEFINED : count;
+        }
+
+        private int maxIndex1() {
+            return maxIndex(true);
+        }
+
+        public int getCount() {
+            if (!countWasExplicitySet && !countValid) {
+                count = max(0, maxIndex(false)); // if there are no cells, the count is zero
+                countValid = true;
+            }
+            return count;
+        }
+
+        public void setCount(int count) {
+            this.count = count;
+            this.countWasExplicitySet = count != UNDEFINED;
+        }
+
+        public boolean isOrderPreserved() {
+            return mOrderPreserved;
+        }
+
+        public void setOrderPreserved(boolean orderPreserved) {
+            mOrderPreserved = orderPreserved;
+            invalidateStructure();
+        }
+
+        private PackedMap<Group, Bounds> createGroupBounds() {
+            int N = getChildCount();
+            Group[] groups = new Group[N];
+            Bounds[] bounds = new Bounds[N];
+            for (int i = 0; i < N; i++) {
+                LayoutParams lp = getLayoutParams(getChildAt(i));
+                Group group = horizontal ? lp.columnGroup : lp.rowGroup;
+
+                groups[i] = group;
+                bounds[i] = new Bounds();
+            }
+
+            return new PackedMap<Group, Bounds>(groups, bounds);
+        }
+
+        private void computeGroupBounds() {
+            for (int i = 0; i < groupBounds.values.length; i++) {
+                groupBounds.values[i].reset();
+            }
+            for (int i = 0, size = getChildCount(); i < size; i++) {
+                View c = getChildAt(i);
+                LayoutParams lp = getLayoutParams(c);
+                Group g = horizontal ? lp.columnGroup : lp.rowGroup;
+
+                Bounds bounds = groupBounds.getValue(i);
+                int dim = getMeasurement(c, horizontal, PRF);
+                // todo test this works correctly when the returned value is UNDEFINED
+                int below = g.alignment.getAlignmentValue(c, dim);
+                int above = dim - below;
+                bounds.include(-below, above);
+            }
+        }
+
+        private PackedMap<Group, Bounds> getGroupBounds() {
+            if (groupBounds == null) {
+                groupBounds = createGroupBounds();
+            }
+            if (!groupBoundsValid) {
+                computeGroupBounds();
+                groupBoundsValid = true;
+            }
+            return groupBounds;
+        }
+
+        // Add values computed by alignment - taking the max of all alignments in each span
+        private PackedMap<Interval, Int> createSpanSizes() {
+            PackedMap<Group, Bounds> groupBounds = getGroupBounds();
+            int N = groupBounds.keys.length;
+            Interval[] spans = new Interval[N];
+            Int[] values = new Int[N];
+            for (int i = 0; i < N; i++) {
+                Interval key = groupBounds.keys[i].span;
+
+                spans[i] = key;
+                values[i] = new Int();
+            }
+            return new PackedMap<Interval, Int>(spans, values);
+        }
+
+        private void computeSpanSizes() {
+            Int[] spans = spanSizes.values;
+            for (int i = 0; i < spans.length; i++) {
+                spans[i].reset();
+            }
+
+            Bounds[] bounds = getGroupBounds().values;  // us get to trigger a re-evaluation
+            for (int i = 0; i < bounds.length; i++) {
+                int value = bounds[i].size();
+
+                Int valueHolder = spanSizes.getValue(i);
+                valueHolder.value = max(valueHolder.value, value);
+            }
+        }
+
+        private PackedMap<Interval, Int> getSpanSizes() {
+            if (spanSizes == null) {
+                spanSizes = createSpanSizes();
+            }
+            if (!spanSizesValid) {
+                computeSpanSizes();
+                spanSizesValid = true;
+            }
+            return spanSizes;
+        }
+
+        private void include(List<Arc> arcs, Interval key, Int size, boolean maximizing) {
+            key = maximizing ? key.inverse() : key;
+            size = maximizing ? size.neg() : size;
+            // this bit below should really be computed outside here -
+            // its just to stop default (col>0) constraints obliterating valid entries
+            for (Arc arc : arcs) {
+                Interval span = arc.span;
+                if (span.equals(key)) {
+                    return;
+                }
+            }
+            arcs.add(new Arc(key, size));
+        }
+
+        private void include2(List<Arc> arcs, Interval span, Int min, Int max,
+                boolean both, boolean maximizing) {
+            include(arcs, span, min, maximizing);
+            if (both) {
+                include(arcs, span.inverse(), max.neg(), maximizing);
+            }
+        }
+
+        private void include2(List<Arc> arcs, Interval span, int min, int max,
+                boolean both, boolean maximizing) {
+            include2(arcs, span, new Int(min), new Int(max), both, maximizing);
+        }
+
+        // Group arcs by their first index, returning an array of arrays.
+        // This is linear in the number of arcs.
+        private Arc[][] index(Arc[] arcs) {
+            int N = getCount() + 1;// the number of vertices
+            Arc[][] result = new Arc[N][];
+            int[] sizes = new int[N];
+            for (Arc arc : arcs) {
+                sizes[arc.span.min]++;
+            }
+            for (int i = 0; i < sizes.length; i++) {
+                result[i] = new Arc[sizes[i]];
+            }
+            // reuse the sizes array to hold the current last elements as we insert each arc
+            Arrays.fill(sizes, 0);
+            for (Arc arc : arcs) {
+                int i = arc.span.min;
+                result[i][sizes[i]++] = arc;
+            }
+
+            return result;
+        }
+
+        // todo do we always add first element?
+        private Arc[] sort(final Arc[] arcs, int start) {
+            final List<Arc> result = new ArrayList<Arc>();
+            new Object() {
+                Arc[][] index = index(arcs);
+                int[] visited = new int[getCount() + 1];
+
+                boolean completesCycle(int loc) {
+                    int state = visited[loc];
+                    if (state == UNVISITED) {
+                        visited[loc] = PENDING;
+                        for (Arc arc : index[loc]) {
+                            Interval span = arc.span;
+                            // the recursive call
+                            if (completesCycle(span.max)) {
+                                // which arcs get set here is dependent on the order
+                                // in which we explore nodes
+                                arc.completesCycle = true;
+                            }
+                            result.add(arc);
+                        }
+                        visited[loc] = COMPLETE;
+                    } else if (state == PENDING) {
+                        return true;
+                    } else if (state == COMPLETE) {
+                    }
+                    return false;
+                }
+            }.completesCycle(start);
+            Collections.reverse(result);
+            assert arcs.length == result.size();
+            return result.toArray(new Arc[result.size()]);
+        }
+
+        private boolean[] findUsed(Collection<Arc> arcs) {
+            boolean[] result = new boolean[getCount()];
+            for (Arc arc : arcs) {
+                Interval span = arc.span;
+                int min = min(span.min, span.max);
+                int max = max(span.min, span.max);
+                for (int i = min; i < max; i++) {
+                    result[i] = true;
+                }
+            }
+            return result;
+        }
+
+        // todo unify with findUsed above
+        private Collection<Interval> getSpacers() {
+            List<Interval> result = new ArrayList<Interval>();
+            int N = getCount() + 1;
+            int[] leadingEdgeCount = new int[N];
+            int[] trailingEdgeCount = new int[N];
+            for (int i = 0, size = getChildCount(); i < size; i++) {
+                LayoutParams lp = getLayoutParams(getChildAt(i));
+                Group g = horizontal ? lp.columnGroup : lp.rowGroup;
+                Interval span = g.span;
+                leadingEdgeCount[span.min]++;
+                trailingEdgeCount[span.max]++;
+            }
+
+            int lastTrailingEdge = 0;
+
+            // treat the parent's edges like peer edges of the opposite type
+            trailingEdgeCount[0] = 1;
+            leadingEdgeCount[N - 1] = 1;
+
+            for (int i = 0; i < N; i++) {
+                if (trailingEdgeCount[i] > 0) {
+                    lastTrailingEdge = i;
+                    continue; // if this is also a leading edge, don't add a space of length zero
+                }
+                if (leadingEdgeCount[i] > 0) {
+                    result.add(new Interval(lastTrailingEdge, i));
+                }
+            }
+            return result;
+        }
+
+        private Arc[] createArcs(boolean maximizing) {
+            List<Arc> spanToSize = new ArrayList<Arc>();
+
+            // Add all the preferred elements that were not defined by the user.
+            PackedMap<Interval, Int> spanSizes = getSpanSizes();
+            for (int i = 0; i < spanSizes.keys.length; i++) {
+                Interval key = spanSizes.keys[i];
+                Int value = spanSizes.values[i];
+                // todo remove value duplicate
+                include2(spanToSize, key, value, value, accommodateBothMinAndMax, maximizing);
+            }
+
+            // Find redundant rows/cols and glue them together with 0-length arcs to link the tree
+            boolean[] used = findUsed(spanToSize);
+            for (int i = 0; i < getCount(); i++) {
+                if (!used[i]) {
+                    Interval span = new Interval(i, i + 1);
+                    include(spanToSize, span, new Int(0), maximizing);
+                    include(spanToSize, span.inverse(), new Int(0), maximizing);
+                }
+            }
+
+            if (mOrderPreserved) {
+                // Add preferred gaps
+                for (int i = 0; i < getCount(); i++) {
+                    if (used[i]) {
+                        include2(spanToSize, new Interval(i, i + 1), 0, 0, false, maximizing);
+                    }
+                }
+            } else {
+                for (Interval gap : getSpacers()) {
+                    include2(spanToSize, gap, 0, 0, false, maximizing);
+                }
+            }
+            Arc[] arcs = spanToSize.toArray(new Arc[spanToSize.size()]);
+            return sort(arcs, maximizing ? getCount() : 0);
+        }
+
+        public Arc[] getArcs(boolean maximizing) {
+            if (arcs == null) {
+                arcs = createArcs(maximizing);
+            }
+            if (!arcsValid) {
+                getSpanSizes();
+                arcsValid = true;
+            }
+            return arcs;
+        }
+
+        private boolean relax(int[] locations, Arc entry, boolean maximizing) {
+            Interval span = entry.span;
+            int u = span.min;
+            int v = span.max;
+            int value = entry.value.value;
+            int candidate = locations[u] + value;
+            if (maximizing ? candidate < locations[v] : candidate > locations[v]) {
+                locations[v] = candidate;
+                return true;
+            }
+            return false;
+        }
+
+        // Bellman-Ford variant
+        private int[] solve(Arc[] arcs, int[] locations, boolean maximizing) {
+            int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
+
+            boolean changed = false;
+            // We take one extra pass over traditional Bellman-Ford (and omit their final step)
+            for (int i = 0; i < N; i++) {
+                changed = false;
+                for (int j = 0, length = arcs.length; j < length; j++) {
+                    changed = changed | relax(locations, arcs[j], maximizing);
+                }
+                if (!changed) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Iteration " + (maximizing ? "(max)" : "(min)") +
+                                " completed after " + (1 + i) + " steps out of " + N);
+                    }
+                    break;
+                }
+            }
+            if (changed) {
+                Log.d(TAG, "*** Algorithm failed to terminate ***");
+            }
+            return locations;
+        }
+
+        private int[] init(int defaultValue, int min, int max) {
+            int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
+            int[] locations = new int[N];
+            Arrays.fill(locations, defaultValue);
+            locations[0] = min;
+            locations[N - 1] = max;
+            return locations;
+        }
+
+        private int[] computeMargins(boolean leading) {
+            int[] result = new int[getCount() + 1];
+            for (int i = 0, size = getChildCount(); i < size; i++) {
+                View c = getChildAt(i);
+                LayoutParams lp = getLayoutParams(c);
+                Group g = horizontal ? lp.columnGroup : lp.rowGroup;
+                Interval span = g.span;
+                int index = leading ? span.min : span.max;
+                result[index] = max(result[index], getMargin(c, leading, horizontal));
+            }
+            return result;
+        }
+
+        // has side effects
+        private void computeLocations(int[] locations, boolean maximizing) {
+            leadingMargins = computeMargins(true);
+            trailingMargins = computeMargins(false);
+
+            solve(getArcs(maximizing), locations, maximizing);
+
+            // Add margins
+            int delta = 0;
+            for (int i = 0; i < getCount(); i++) {
+                int margins = leadingMargins[i] + trailingMargins[i + 1];
+                delta += margins;
+                locations[i + 1] += delta;
+            }
+        }
+
+        private int size(int[] locations) {
+            return locations[locations.length - 1] - locations[0];
+        }
+
+        private int[] getLimit(boolean lowerBound, boolean maximizing) {
+            int defaultValue = maximizing ? MAX_VALUE : MIN_VALUE;
+            if (lowerBound) {
+                // as long as it avoids overflow, the upper bound can be anything (including zero)
+                int[] result = init(defaultValue, defaultValue, 1000);
+                computeLocations(result, maximizing);
+                int delta = result[0];
+                for (int i = 0; i < result.length; i++) {
+                    result[i] -= delta;
+                }
+                return result;
+            } else {
+                int[] result = init(defaultValue, 0, defaultValue);
+                computeLocations(result, maximizing);
+                return result;
+            }
+        }
+
+        // External entry points
+
+        private int getMin() {
+            int[] mins = getLimit(maximizing, maximizing);
+            return size(mins);
+        }
+
+        private int getPref() {
+            return accommodateBothMinAndMax ? getMax() : getMin();
+        }
+
+        private int getMax() {
+            int[] maxs = getLimit(!maximizing, maximizing);
+            return size(maxs);
+        }
+
+        private int totalMarginSize() {
+            return sum(leadingMargins) + sum(trailingMargins);
+        }
+
+        private void layout(int targetSize) {
+            int N = getCount() + 1;
+            int min = getMin();
+            int max = getMax();
+
+            int clippedTargetSize = max(min(max, targetSize), min); // confine size to valid range
+
+            if (DEBUG) {
+                Log.d(TAG, "Computing sizes for target " + clippedTargetSize + " for " +
+                        (horizontal ? "col" : "row") + "s from: " + arcs);
+            }
+            int delta = clippedTargetSize - min;
+            prefSizeOfWeightedComponent = delta;
+            invalidateValues();
+            int defaultValue = maximizing ? MAX_VALUE : MIN_VALUE;
+            locations = init(defaultValue, 0, clippedTargetSize - totalMarginSize());
+            computeLocations(locations, maximizing);
+            prefSizeOfWeightedComponent = 0;
+
+            if (DEBUG) {
+                Log.d(TAG, "locations = " + Arrays.toString(locations));
+                int[] computedSizes = new int[N - 1];
+                for (int i = 0; i < N - 1; i++) {
+                    computedSizes[i] = locations[i + 1] - locations[i];
+                }
+                Log.d(TAG, "sizes = " + Arrays.toString(computedSizes));
+            }
+        }
+
+        private void invalidateStructure() {
+            countValid = false;
+            groupBounds = null;
+            spanSizes = null;
+
+            invalidateValues();
+        }
+
+        private void invalidateValues() {
+            groupBoundsValid = false;
+            spanSizesValid = false;
+            arcsValid = false;
+        }
+    }
+
+    /**
+     * Layout information associated with each of the children of a GridLayout.
+     * <p>
+     * GridLayout supports both row and column spanning and arbitrary forms of alignment within
+     * each cell group. The fundamental parameters associated with each cell group are
+     * gathered into their vertical and horizontal components and stored
+     * in the {@link #rowGroup} and {@link #columnGroup} layout parameters.
+     * {@link Group Groups} are immutable structures and may be shared between the layout
+     * parameters of different children.
+     * <p>
+     * The {@link Group#span span} fields of the row and column groups together specify
+     * the four grid indices that delimit the cells of this cell group.
+     * <p>
+     * The {@link Group#alignment alignment} fields of the row and column groups together specify
+     * both aspects of alignment within the cell group. It is also possible to specify a child's
+     * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
+     * method.
+     * <p>
+     * See {@link GridLayout} for a description of the conventions used by GridLayout
+     * in reference to grid indices.
+     *
+     * <h4>Default values</h4>
+     *
+     * <ul>
+     *     <li>{@link #width} = {@link #WRAP_CONTENT}</li>
+     *     <li>{@link #height} = {@link #WRAP_CONTENT}</li>
+     *     <li>{@link #topMargin} = 0 when
+     *          {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
+     *          <code>false</code>; otherwise {@link Integer#MIN_VALUE}, to
+     *          indicate that a default value should be computed on demand. </li>
+     *     <li>{@link #leftMargin} = 0 when
+     *          {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
+     *          <code>false</code>; otherwise {@link Integer#MIN_VALUE}, to
+     *          indicate that a default value should be computed on demand. </li>
+     *     <li>{@link #bottomMargin} = 0 when
+     *          {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
+     *          <code>false</code>; otherwise {@link Integer#MIN_VALUE}, to
+     *          indicate that a default value should be computed on demand. </li>
+     *     <li>{@link #rightMargin} = 0 when
+     *          {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is
+     *          <code>false</code>; otherwise {@link Integer#MIN_VALUE}, to
+     *          indicate that a default value should be computed on demand. </li>
+     *     <li>{@link #rowGroup}<code>.span</code> = <code>[0, 1]</code> </li>
+     *     <li>{@link #rowGroup}<code>.alignment</code> = {@link #BASELINE} </li>
+     *     <li>{@link #columnGroup}<code>.span</code> = <code>[0, 1]</code> </li>
+     *     <li>{@link #columnGroup}<code>.alignment</code> = {@link #LEFT} </li>
+     *     <li>{@link #rowWeight} = <code>0f</code> </li>
+     *     <li>{@link #columnWeight} = <code>0f</code> </li>
+     * </ul>
+     *
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_row
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_column
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
+     */
+    public static class LayoutParams extends MarginLayoutParams {
+
+        // Default values
+
+        private static final int DEFAULT_WIDTH = WRAP_CONTENT;
+        private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
+        private static final int DEFAULT_MARGIN = UNDEFINED;
+        private static final int DEFAULT_ROW = UNDEFINED;
+        private static final int DEFAULT_COLUMN = UNDEFINED;
+        private static final Interval DEFAULT_SPAN = new Interval(0, 1);
+        private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
+        private static final Alignment DEFAULT_HORIZONTAL_ALIGNMENT = LEFT;
+        private static final Alignment DEFAULT_VERTCIAL_ALGIGNMENT = BASELINE;
+        private static final Group DEFAULT_HORIZONTAL_GROUP =
+                new Group(DEFAULT_SPAN, DEFAULT_HORIZONTAL_ALIGNMENT);
+        private static final Group DEFAULT_VERTICAL_GROUP =
+                new Group(DEFAULT_SPAN, DEFAULT_VERTCIAL_ALGIGNMENT);
+        private static final int DEFAULT_WEIGHT = 0;
+
+        // Misc
+
+        private static final Rect CONTAINER_BOUNDS = new Rect(0, 0, 2, 2);
+        private static final Alignment[] HORIZONTAL_ALIGNMENTS = { LEFT, CENTER, RIGHT };
+        private static final Alignment[] VERTICAL_ALIGNMENTS = { TOP, CENTER, BOTTOM };
+
+        // TypedArray indices
+
+        private static final int MARGIN = styleable.ViewGroup_MarginLayout_layout_margin;
+        private static final int LEFT_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginLeft;
+        private static final int TOP_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginTop;
+        private static final int RIGHT_MARGIN = styleable.ViewGroup_MarginLayout_layout_marginRight;
+        private static final int BOTTOM_MARGIN =
+                styleable.ViewGroup_MarginLayout_layout_marginBottom;
+
+        private static final int COLUMN = styleable.GridLayout_Layout_layout_column;
+        private static final int COLUMN_SPAN = styleable.GridLayout_Layout_layout_columnSpan;
+        private static final int COLUMN_WEIGHT = styleable.GridLayout_Layout_layout_columnWeight;
+        private static final int ROW = styleable.GridLayout_Layout_layout_row;
+        private static final int ROW_SPAN = styleable.GridLayout_Layout_layout_rowSpan;
+        private static final int ROW_WEIGHT = styleable.GridLayout_Layout_layout_rowWeight;
+        private static final int GRAVITY = styleable.GridLayout_Layout_layout_gravity;
+
+        // Instance variables
+
+        /**
+         * The group that specifies the vertical characteristics of the cell group
+         * described by these layout parameters.
+         */
+        public Group rowGroup;
+        /**
+         * The group that specifies the horizontal characteristics of the cell group
+         * described by these layout parameters.
+         */
+        public Group columnGroup;
+        /**
+         * The proportional space that should be taken by the associated row group
+         * during excess space distribution.
+         */
+        public float rowWeight;
+        /**
+         * The proportional space that should be taken by the associated column group
+         * during excess space distribution.
+         */
+        public float columnWeight;
+
+        // Constructors
+
+        private LayoutParams(
+                int width, int height,
+                int left, int top, int right, int bottom,
+                Group rowGroup, Group columnGroup, float rowWeight, float columnWeight) {
+            super(width, height);
+            setMargins(left, top, right, bottom);
+            this.rowGroup = rowGroup;
+            this.columnGroup = columnGroup;
+            this.rowWeight = rowWeight;
+            this.columnWeight = columnWeight;
+        }
+
+        /**
+         * Constructs a new LayoutParams instance for this <code>rowGroup</code>
+         * and <code>columnGroup</code>. All other fields are initialized with
+         * default values as defined in {@link LayoutParams}.
+         *
+         * @param rowGroup    the rowGroup
+         * @param columnGroup the columnGroup
+         */
+        public LayoutParams(Group rowGroup, Group columnGroup) {
+            this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
+                    DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
+                    rowGroup, columnGroup, DEFAULT_WEIGHT, DEFAULT_WEIGHT);
+        }
+
+        /**
+         * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
+         */
+        public LayoutParams() {
+            this(DEFAULT_HORIZONTAL_GROUP, DEFAULT_VERTICAL_GROUP);
+        }
+
+        // Copying constructors
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams params) {
+            super(params);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(MarginLayoutParams params) {
+            super(params);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(LayoutParams that) {
+            super(that);
+            this.columnGroup = that.columnGroup;
+            this.rowGroup = that.rowGroup;
+            this.columnWeight = that.columnWeight;
+            this.rowWeight = that.rowWeight;
+        }
+
+        // AttributeSet constructors
+
+        private LayoutParams(Context context, AttributeSet attrs, int defaultGravity) {
+            super(context, attrs);
+            reInitSuper(context, attrs);
+            init(context, attrs, defaultGravity);
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * Values not defined in the attribute set take the default values
+         * defined in {@link LayoutParams}.
+         */
+        public LayoutParams(Context context, AttributeSet attrs) {
+            this(context, attrs, Gravity.NO_GRAVITY);
+        }
+
+        // Implementation
+
+        private static boolean definesVertical(int gravity) {
+            return gravity > 0 && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != 0;
+        }
+
+        private static boolean definesHorizontal(int gravity) {
+            return gravity > 0 && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != 0;
+        }
+
+        private static <T> T getAlignment(T[] alignments, T fill, int min, int max,
+                boolean isUndefined, T defaultValue) {
+            if (isUndefined) {
+                return defaultValue;
+            }
+            return min != max ? fill : alignments[min];
+        }
+
+        // Reinitialise the margins using a different default policy than MarginLayoutParams.
+        // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
+        // so that a layout manager default can be accessed post set up. We need this as, at the
+        // point of installation, we do not know how many rows/cols there are and therefore
+        // which elements are positioned next to the container's trailing edges. We need to
+        // know this as margins around the container's boundary should have different
+        // defaults to those between peers.
+
+        // This method could be parametrized and moved into MarginLayout.
+        private void reInitSuper(Context context, AttributeSet attrs) {
+            TypedArray a = context.obtainStyledAttributes(attrs, styleable.ViewGroup_MarginLayout);
+            try {
+                int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
+
+                this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
+                this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
+                this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
+                this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        // Gravity. For conversion from the static the integers defined in the Gravity class,
+        // use Gravity.apply() to apply gravity to a view of zero size and see where it ends up.
+        private static Alignment getHorizontalAlignment(int gravity, int width) {
+            Rect r = new Rect(0, 0, 0, 0);
+            Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r);
+
+            boolean fill = width == MATCH_PARENT;
+            Alignment defaultAlignment = fill ? FILL : DEFAULT_HORIZONTAL_ALIGNMENT;
+            return getAlignment(HORIZONTAL_ALIGNMENTS, FILL, r.left, r.right,
+                    !definesHorizontal(gravity), defaultAlignment);
+        }
+
+        private static Alignment getVerticalAlignment(int gravity, int height) {
+            Rect r = new Rect(0, 0, 0, 0);
+            Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r);
+
+            boolean fill = height == MATCH_PARENT;
+            Alignment defaultAlignment = fill ? FILL : DEFAULT_VERTCIAL_ALGIGNMENT;
+            return getAlignment(VERTICAL_ALIGNMENTS, FILL, r.top, r.bottom,
+                    !definesVertical(gravity), defaultAlignment);
+        }
+
+        private void init(Context context, AttributeSet attrs, int defaultGravity) {
+            TypedArray a = context.obtainStyledAttributes(attrs, styleable.GridLayout_Layout);
+            try {
+                int gravity = a.getInteger(GRAVITY, defaultGravity);
+
+                int column = a.getInteger(COLUMN, DEFAULT_COLUMN);
+                int width = a.getInteger(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
+                Interval colSpan = new Interval(column, column + width);
+                this.columnGroup = new Group(colSpan, getHorizontalAlignment(gravity, width));
+                this.columnWeight = a.getFloat(COLUMN_WEIGHT, DEFAULT_WEIGHT);
+
+                int row = a.getInteger(ROW, DEFAULT_ROW);
+                int height = a.getInteger(ROW_SPAN, DEFAULT_SPAN_SIZE);
+                Interval rowSpan = new Interval(row, row + height);
+                this.rowGroup = new Group(rowSpan, getVerticalAlignment(gravity, height));
+                this.rowWeight = a.getFloat(ROW_WEIGHT, DEFAULT_WEIGHT);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        /**
+         * Describes how the child views are positioned. Default is <code>LEFT | BASELINE</code>.
+         *
+         * @param gravity the new gravity. See {@link android.view.Gravity}.
+         *
+         * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
+         */
+        public void setGravity(int gravity) {
+            columnGroup = columnGroup.copyWriteAlignment(getHorizontalAlignment(gravity, width));
+            rowGroup = rowGroup.copyWriteAlignment(getVerticalAlignment(gravity, height));
+        }
+
+        @Override
+        protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
+            this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
+            this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
+        }
+
+        private void setVerticalGroupSpan(Interval span) {
+            rowGroup = rowGroup.copyWriteSpan(span);
+        }
+
+        private void setHorizontalGroupSpan(Interval span) {
+            columnGroup = columnGroup.copyWriteSpan(span);
+        }
+    }
+
+    private static class Arc {
+        public final Interval span;
+        public final Int value;
+        public boolean completesCycle;
+
+        public Arc(Interval span, Int value) {
+            this.span = span;
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return span + " " + (completesCycle ? "+>" : "->") + " " + value;
+        }
+    }
+
+    // A mutable Integer - used to avoid heap allocation during the layout operation
+
+    private static class Int {
+        public int value;
+
+        private Int() {
+            reset();
+        }
+
+        private Int(int value) {
+            this.value = value;
+        }
+
+        private void reset() {
+            value = Integer.MIN_VALUE;
+        }
+
+        private Int neg() {
+            // this should never be called
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    @SuppressWarnings(value = "unchecked")
+    private static class PackedMap<K, V> {
+        public final int[] index;
+        public final K[] keys;
+        public final V[] values;
+
+        private PackedMap(K[] keys, V[] values) {
+            this.index = createIndex(keys);
+
+            this.keys = index(keys, index);
+            this.values = index(values, index);
+        }
+
+        private K getKey(int i) {
+            return keys[index[i]];
+        }
+
+        private V getValue(int i) {
+            return values[index[i]];
+        }
+
+        private static <K> int[] createIndex(K[] keys) {
+            int size = keys.length;
+            int[] result = new int[size];
+
+            Map<K, Integer> keyToIndex = new HashMap<K, Integer>();
+            for (int i = 0; i < size; i++) {
+                K key = keys[i];
+                Integer index = keyToIndex.get(key);
+                if (index == null) {
+                    index = keyToIndex.size();
+                    keyToIndex.put(key, index);
+                }
+                result[i] = index;
+            }
+            return result;
+        }
+
+        private static int max(int[] a, int valueIfEmpty) {
+            int result = valueIfEmpty;
+            for (int i = 0, length = a.length; i < length; i++) {
+                result = Math.max(result, a[i]);
+            }
+            return result;
+        }
+
+        private static <K> K[] index(K[] keys, int[] index) {
+            int size = keys.length;
+            Class<?> componentType = keys.getClass().getComponentType();
+            K[] result = (K[]) Array.newInstance(componentType, max(index, -1) + 1);
+
+            // this overwrite duplicates, retaining the last equivalent entry
+            for (int i = 0; i < size; i++) {
+                result[index[i]] = keys[i];
+            }
+            return result;
+        }
+    }
+
+    private static class Bounds {
+        public int below;
+        public int above;
+
+        private Bounds(int below, int above) {
+            this.below = below;
+            this.above = above;
+        }
+
+        private Bounds() {
+            reset();
+        }
+
+        private void reset() {
+            below = Integer.MAX_VALUE;
+            above = Integer.MIN_VALUE;
+        }
+
+        private void include(int below, int above) {
+            this.below = min(this.below, below);
+            this.above = max(this.above, above);
+        }
+
+        private int size() {
+            return above - below;
+        }
+
+        @Override
+        public String toString() {
+            return "Bounds{" +
+                    "below=" + below +
+                    ", above=" + above +
+                    '}';
+        }
+    }
+
+    /**
+     * An Interval represents a contiguous range of values that lie between
+     * the interval's {@link #min} and {@link #max} values.
+     * <p>
+     * Intervals are immutable so may be passed as values and used as keys in hash tables.
+     * It is not necessary to have multiple instances of Intervals which have the same
+     * {@link #min} and {@link #max} values.
+     * <p>
+     * Intervals are often written as <code>[min, max]</code> and represent the set of values
+     * <em>x</em> such that <em>min <= x < max</em>.
+     */
+    public static class Interval {
+        /**
+         * The minimum value.
+         */
+        public final int min;
+        /**
+         * The maximum value.
+         */
+        public final int max;
+
+        /**
+         * Construct a new Interval, <code>interval</code>, where:
+         * <ul>
+         *     <li> <code>interval.min = min</code> </li>
+         *     <li> <code>interval.max = max</code> </li>
+         * </ul>
+         *
+         * @param min the minimum value.
+         * @param max the maximum value.
+         */
+        public Interval(int min, int max) {
+            this.min = min;
+            this.max = max;
+        }
+
+        private int size() {
+            return max - min;
+        }
+
+        private Interval inverse() {
+            return new Interval(max, min);
+        }
+
+        /**
+         * Returns true if the {@link #getClass class}, {@link #min} and {@link #max} properties
+         * of this Interval and the supplied parameter are pairwise equal; false otherwise.
+         *
+         * @param that the object to compare this interval with.
+         *
+         * @return {@code true} if the specified object is equal to this
+         *         {@code Interval}; {@code false} otherwise.
+         */
+        @Override
+        public boolean equals(Object that) {
+            if (this == that) {
+                return true;
+            }
+            if (that == null || getClass() != that.getClass()) {
+                return false;
+            }
+
+            Interval interval = (Interval) that;
+
+            if (max != interval.max) {
+                return false;
+            }
+            if (min != interval.min) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = min;
+            result = 31 * result + max;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "[" + min + ", " + max + "]";
+        }
+    }
+
+    /**
+     * A group specifies either the horizontal or vertical characteristics of a group of
+     * cells.
+     * <p>
+     * Groups are immutable and so may be shared between views with the same
+     * <code>span</code> and <code>alignment</code>.
+     */
+    public static class Group {
+        /**
+         * The {@link Interval#min min} and {@link Interval#max max} values of
+         * a span specify the grid indices of the leading and trailing edges of
+         * the cell group.
+         * <p>
+         * See {@link GridLayout} for a description of the conventions used by GridLayout
+         * for grid indices.
+         */
+        public final Interval span;
+        /**
+         * Specifies how cells should be aligned in this group.
+         * For row groups, this specifies the vertical alignment.
+         * For column groups, this specifies the horizontal alignment.
+         */
+        public final Alignment alignment;
+
+        /**
+         * Construct a new Group, <code>group</code>, where:
+         * <ul>
+         *     <li> <code>group.span = span</code> </li>
+         *     <li> <code>group.alignment = alignment</code> </li>
+         * </ul>
+         *
+         * @param span      the span.
+         * @param alignment the alignment.
+         */
+        public Group(Interval span, Alignment alignment) {
+            this.span = span;
+            this.alignment = alignment;
+        }
+
+        /**
+         * Construct a new Group, <code>group</code>, where:
+         * <ul>
+         *     <li> <code>group.span = [min, max]</code> </li>
+         *     <li> <code>group.alignment = alignment</code> </li>
+         * </ul>
+         *
+         * @param min       the minimum.
+         * @param max       the maximum.
+         * @param alignment the alignment.
+         */
+        public Group(int min, int max, Alignment alignment) {
+            this(new Interval(min, max), alignment);
+        }
+
+        /**
+         * Construct a new Group, <code>group</code>, where:
+         * <ul>
+         *     <li> <code>group.span = [min, min + 1]</code> </li>
+         *     <li> <code>group.alignment = alignment</code> </li>
+         * </ul>
+         *
+         * @param min       the minimum.
+         * @param alignment the alignment.
+         */
+        public Group(int min, Alignment alignment) {
+            this(min, min + 1, alignment);
+        }
+
+        private Group copyWriteSpan(Interval span) {
+            return new Group(span, alignment);
+        }
+
+        private Group copyWriteAlignment(Alignment alignment) {
+            return new Group(span, alignment);
+        }
+
+        /**
+         * Returns true if the {@link #getClass class}, {@link #alignment} and {@link #span}
+         * properties of this Group and the supplied parameter are pairwise equal; false otherwise.
+         *
+         * @param that the object to compare this group with.
+         *
+         * @return {@code true} if the specified object is equal to this
+         *         {@code Group}; {@code false} otherwise.
+         */
+        @Override
+        public boolean equals(Object that) {
+            if (this == that) {
+                return true;
+            }
+            if (that == null || getClass() != that.getClass()) {
+                return false;
+            }
+
+            Group group = (Group) that;
+
+            if (!alignment.equals(group.alignment)) {
+                return false;
+            }
+            if (!span.equals(group.span)) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = span.hashCode();
+            result = 31 * result + alignment.hashCode();
+            return result;
+        }
+    }
+
+    // Alignments
+
+
+    /**
+     * Alignments specify where a view should be placed within a cell group and
+     * what size it should be.
+     * <p>
+     * The {@link LayoutParams} class contains a {@link LayoutParams#rowGroup rowGroup}
+     * and a {@link LayoutParams#columnGroup columnGroup} each of which contains an
+     * {@link Group#alignment alignment}. Overall placement of the view in the cell
+     * group is specified by the two alignments which act along each axis independently.
+     * <p>
+     * An Alignment implementation must define the {@link #getAlignmentValue(View, int)}
+     * to return the appropriate value for the type of alignment being defined.
+     * The enclosing algorithms position the children
+     * so that the values returned from the alignment
+     * are the same for all of the views in a group.
+     * <p>
+     *  The GridLayout class defines the most common alignments used in general layout:
+     * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #CENTER}, {@link
+     * #BASELINE} and {@link #FILL}.
+     */
+    public static interface Alignment {
+        /**
+         * Returns an alignment value. In the case of vertical alignments the value
+         * returned should indicate the distance from the top of the view to the
+         * alignment location.
+         * For horizontal alignments measurement is made from the left edge of the component.
+         *
+         * @param view     the view to which this alignment should be applied.
+         * @param viewSize the measured size of the view.
+         * @return the alignment value.
+         */
+        public int getAlignmentValue(View view, int viewSize);
+
+        /**
+         * Returns the size of the view specified by this alignment.
+         * In the case of vertical alignments this method should return a height; for
+         * horizontal alignments this method should return the width.
+         *
+         * @param view     the view to which this alignment should be applied.
+         * @param viewSize the measured size of the view.
+         * @param cellSize the size of the cell into which this view will be placed.
+         * @return the aligned size.
+         */
+        public int getSizeInCell(View view, int viewSize, int cellSize);
+    }
+
+    private static abstract class AbstractAlignment implements Alignment {
+        public int getSizeInCell(View view, int viewSize, int cellSize) {
+            return viewSize;
+        }
+    }
+
+    private static final Alignment LEADING = new AbstractAlignment() {
+        public int getAlignmentValue(View view, int viewSize) {
+            return 0;
+        }
+
+    };
+
+    private static final Alignment TRAILING = new AbstractAlignment() {
+        public int getAlignmentValue(View view, int viewSize) {
+            return viewSize;
+        }
+    };
+
+    /**
+     * Indicates that a view should be aligned with the <em>top</em>
+     * edges of the other views in its cell group.
+     */
+    public static final Alignment TOP = LEADING;
+
+    /**
+     * Indicates that a view should be aligned with the <em>bottom</em>
+     * edges of the other views in its cell group.
+     */
+    public static final Alignment BOTTOM = TRAILING;
+
+    /**
+     * Indicates that a view should be aligned with the <em>right</em>
+     * edges of the other views in its cell group.
+     */
+    public static final Alignment RIGHT = TRAILING;
+
+    /**
+     * Indicates that a view should be aligned with the <em>left</em>
+     * edges of the other views in its cell group.
+     */
+    public static final Alignment LEFT = LEADING;
+
+    /**
+     * Indicates that a view should be <em>centered</em> with the other views in its cell group.
+     * This constant may be used in both {@link LayoutParams#rowGroup rowGroups} and {@link
+     * LayoutParams#columnGroup columnGroups}.
+     */
+    public static final Alignment CENTER = new AbstractAlignment() {
+        public int getAlignmentValue(View view, int viewSize) {
+            return viewSize >> 1;
+        }
+    };
+
+    /**
+     * Indicates that a view should be aligned with the <em>baselines</em>
+     * of the other views in its cell group.
+     * This constant may only be used as an alignment in {@link LayoutParams#rowGroup rowGroups}.
+     *
+     * @see View#getBaseline()
+     */
+    public static final Alignment BASELINE = new AbstractAlignment() {
+        public int getAlignmentValue(View view, int viewSize) {
+            if (view == null) {
+                return UNDEFINED;
+            }
+            // todo do we need to call measure first?
+            int baseline = view.getBaseline();
+            return baseline == -1 ? UNDEFINED : baseline;
+        }
+
+    };
+
+    /**
+     * Indicates that a view should expanded to fit the boundaries of its cell group.
+     * This constant may be used in both {@link LayoutParams#rowGroup rowGroups} and
+     * {@link LayoutParams#columnGroup columnGroups}.
+     */
+    public static final Alignment FILL = new Alignment() {
+        public int getAlignmentValue(View view, int viewSize) {
+            return UNDEFINED;
+        }
+
+        public int getSizeInCell(View view, int viewSize, int cellSize) {
+            return cellSize;
+        }
+    };
+}
\ No newline at end of file
diff --git a/core/java/android/widget/Space.java b/core/java/android/widget/Space.java
new file mode 100644
index 0000000..d98b937
--- /dev/null
+++ b/core/java/android/widget/Space.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Space is a lightweight View subclass that may be used to create gaps between components
+ * in general purpose layouts.
+ */
+public final class Space extends View {
+    /**
+     * {@inheritDoc}
+     */
+    public Space(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Space(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Space(Context context) {
+        super(context);
+    }
+
+    /**
+     * Draw nothing.
+     *
+     * @param canvas an unused parameter.
+     */
+    @Override
+    public void draw(Canvas canvas) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ViewGroup.LayoutParams getLayoutParams() {
+        return super.getLayoutParams();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        super.setLayoutParams(params);
+    }
+}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c1e81c3..f854e93 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2429,6 +2429,36 @@
         <!-- Size of padding on either end of a divider. -->
         <attr name="dividerPadding" format="dimension" />
     </declare-styleable>
+    <declare-styleable name="GridLayout">
+        <!-- The orientation property is not used during layout. It is only used to
+        allocate row and column prameters when they are not specified by its children's
+        layout paramters. GridLayout works like LinearLayout in this case;
+        putting all the components either in a single row or in a single column -
+        depending on the value of this flag. In the horozintal case, a columnCount
+        property may be additionally supplied to force new rows to be created when a
+        row is full. The rowCount attribute may be used similarly in the vertical case.
+        The default is horizontal. -->
+        <attr name="orientation" />
+        <!-- The maxmimum number of rows to create when automatically positioning children. -->
+        <attr name="rowCount" format="integer" />
+        <!-- The maxmimum number of columns to create when automatically positioning children. -->
+        <attr name="columnCount" format="integer" />
+        <!-- When set to true, tells GridLayout to use default margins when none are specified
+        in a view's layout parameters.
+        The default value is false.
+        See {@link android.widget.GridLayout#setUseDefaultMargins(boolean)}.-->
+        <attr name="useDefaultMargins" format="boolean" />
+        <!-- When set to true, forces row boundaries to appear in the same order
+        as row indices.
+        The default is false.
+        See {@link android.widget.GridLayout#setRowOrderPreserved(boolean)}.-->
+        <attr name="rowOrderPreserved" format="boolean" />
+        <!-- When set to true, forces column boundaries to appear in the same order
+        as column indices.
+        The default is false.
+        See {@link android.widget.GridLayout#setColumnOrderPreserved(boolean)}.-->
+        <attr name="columnOrderPreserved" format="boolean" />
+    </declare-styleable>
     <declare-styleable name="ListView">
         <!-- Reference to an array resource that will populate the ListView.  For static content,
              this is simpler than populating the ListView programmatically. -->
@@ -3128,6 +3158,38 @@
         <attr name="layout_weight" format="float" />
         <attr name="layout_gravity" />
     </declare-styleable>
+    <declare-styleable name="GridLayout_Layout">
+        <!-- The row boundary delimiting the top of the group of cells
+        occupied by this view. -->
+        <attr name="layout_row" format="integer" />
+        <!-- The row span: the difference between the bottom and top
+        boundaries delimiting the group of cells occupied by this view.
+        The default is one.
+        See {@link android.widget.GridLayout.Group#span}. -->
+        <attr name="layout_rowSpan" format="integer" min="1" />
+        <!-- A number indicating the relative proportion of availible space that
+        should be taken by this group of cells.
+        The default is zero.
+        See {@link android.widget.GridLayout.LayoutParams#columnWeight}. -->
+        <attr name="layout_rowWeight" format="float" />
+        <!-- The column boundary delimiting the left of the group of cells
+        occupied by this view. -->
+        <attr name="layout_column" />
+        <!-- The column span: the difference between the right and left
+        boundaries delimiting the group of cells occupied by this view.
+        The default is one.
+        See {@link android.widget.GridLayout.Group#span}. -->
+        <attr name="layout_columnSpan" format="integer" min="1" />
+        <!-- A number indicating the relative proportion of availible space that
+        should be taken by this group of cells.
+        The default is zero.
+        See {@link android.widget.GridLayout.LayoutParams#columnWeight}.-->
+        <attr name="layout_columnWeight" format="float" />
+        <!-- Gravity specifies how a component should be placed in its group of cells.
+        The default is LEFT | BASELINE.
+        See {@link android.widget.GridLayout.LayoutParams#setGravity(int)}. -->
+        <attr name="layout_gravity" />
+    </declare-styleable>
     <declare-styleable name="FrameLayout_Layout">
         <attr name="layout_gravity" />
     </declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1957b2a..0bd939ed 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1672,4 +1672,17 @@
   <public type="attr" name="fullBackupAgent" />
   <public type="attr" name="suggestionsEnabled" />
 
+  <public type="attr" name="rowCount" />
+  <public type="attr" name="rowOrderPreserved" />
+  <public type="attr" name="columnCount" />
+  <public type="attr" name="columnOrderPreserved" />
+  <public type="attr" name="useDefaultMargins" />
+
+  <public type="attr" name="layout_row" />
+  <public type="attr" name="layout_rowSpan" />
+
+  <public type="attr" name="layout_rowWeight" />
+  <public type="attr" name="layout_columnSpan" />
+  <public type="attr" name="layout_columnWeight" />
+
 </resources>
diff --git a/tests/GridLayoutTest/Android.mk b/tests/GridLayoutTest/Android.mk
new file mode 100644
index 0000000..a02918b
--- /dev/null
+++ b/tests/GridLayoutTest/Android.mk
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2010 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := GridLayoutTest
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/GridLayoutTest/AndroidManifest.xml b/tests/GridLayoutTest/AndroidManifest.xml
new file mode 100644
index 0000000..53ca4ce
--- /dev/null
+++ b/tests/GridLayoutTest/AndroidManifest.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.test.layout">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-sdk android:minSdkVersion="11"/>
+
+    <application>
+        <activity android:name="Activity0" android:label="Activity0">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="Activity1" android:label="Activity1">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="Activity2" android:label="Activity2">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="Activity3" android:label="Activity3">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="Activity4" android:label="Activity4">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="Activity5" android:label="Activity5">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="Activity6" android:label="Activity6">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="Activity7" android:label="Activity7">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+
+</manifest>
diff --git a/tests/GridLayoutTest/res/layout/grid0.xml b/tests/GridLayoutTest/res/layout/grid0.xml
new file mode 100644
index 0000000..1c291f0
--- /dev/null
+++ b/tests/GridLayoutTest/res/layout/grid0.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent">
+</GridLayout>
\ No newline at end of file
diff --git a/tests/GridLayoutTest/res/layout/grid3.xml b/tests/GridLayoutTest/res/layout/grid3.xml
new file mode 100644
index 0000000..ace7b4c
--- /dev/null
+++ b/tests/GridLayoutTest/res/layout/grid3.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<GridLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        >
+
+    <Button
+            android:text="fill"
+            android:width="200dip"
+            android:height="100dip"
+            android:layout_marginLeft="50dip"
+            android:layout_row="0"
+            android:layout_column="0"
+            android:layout_gravity="fill_horizontal"
+            />
+
+    <EditText
+            android:layout_row="0"
+            android:layout_column="1"
+            />
+
+    <Button
+            android:text="left"
+            android:layout_row="1"
+            android:layout_column="0"
+            />
+
+    <EditText
+            android:layout_row="1"
+            android:layout_column="1"
+            />
+
+    <Button
+            android:text="right"
+            android:layout_row="2"
+            android:layout_column="0"
+            android:layout_gravity="right"
+            />
+
+    <EditText
+            android:layout_margin="50dip"
+            android:textSize="100dip"
+            android:layout_row="2"
+            android:layout_column="1"
+            />
+
+    <Button
+            android:text="center"
+            android:layout_row="3"
+            android:layout_column="0"
+            android:layout_gravity="center_horizontal"
+            />
+
+    <EditText
+            android:layout_height="fill_parent"
+            android:layout_row="3"
+            android:layout_column="1"
+            />
+
+</GridLayout>
diff --git a/tests/GridLayoutTest/res/layout/grid4.xml b/tests/GridLayoutTest/res/layout/grid4.xml
new file mode 100644
index 0000000..116a256
--- /dev/null
+++ b/tests/GridLayoutTest/res/layout/grid4.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<GridLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <Button
+            android:text="fill"
+            android:layout_margin="100dip"
+            android:layout_marginLeft="100dip"
+            android:layout_marginBottom="100dip"
+            android:layout_marginTop="100dip"
+            android:layout_marginRight="100dip"
+            android:layout_row="0"
+            android:layout_column="0"
+            android:layout_gravity="fill_horizontal"
+            />
+</GridLayout>
diff --git a/tests/GridLayoutTest/res/layout/grid5.xml b/tests/GridLayoutTest/res/layout/grid5.xml
new file mode 100644
index 0000000..9b87f18
--- /dev/null
+++ b/tests/GridLayoutTest/res/layout/grid5.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<GridLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+
+        android:orientation="horizontal"
+        android:columnCount="2"
+        >
+
+    <TextView
+            android:text="Name:"
+            />
+
+    <EditText
+            android:text="fill"
+            android:width="200dip"
+            android:height="100dip"
+            android:layout_marginLeft="50dip"
+            android:layout_gravity="fill_horizontal"
+            />
+
+    <TextView
+            />
+
+<!--
+    <TextView
+            android:layout_row_weight="1"
+            android:layout_column_weight="1"
+            />
+-->
+
+    <EditText
+            android:text="left"
+            />
+
+    <EditText/>
+
+    <EditText
+            android:text="right"
+            android:layout_gravity="right"
+            />
+
+    <EditText
+            android:layout_margin="50dip"
+            android:textSize="100dip"
+            />
+
+    <Button
+            android:text="center"
+            android:layout_gravity="center_horizontal"
+            />
+
+    <EditText
+            android:layout_height="fill_parent"
+            />
+
+</GridLayout>
diff --git a/tests/GridLayoutTest/res/layout/grid6.xml b/tests/GridLayoutTest/res/layout/grid6.xml
new file mode 100644
index 0000000..7486a2d
--- /dev/null
+++ b/tests/GridLayoutTest/res/layout/grid6.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<GridLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+
+        android:columnCount="2"
+        android:useDefaultMargins="true"
+        >
+
+    <TextView android:text="Name:"/>
+
+    <EditText/>
+
+    <TextView android:text="Rank:"/>
+
+    <EditText/>
+
+    <TextView android:text="Serial Number:"/>
+
+    <EditText/>
+
+</GridLayout>
diff --git a/tests/GridLayoutTest/res/layout/grid7.xml b/tests/GridLayoutTest/res/layout/grid7.xml
new file mode 100644
index 0000000..b97a00b
--- /dev/null
+++ b/tests/GridLayoutTest/res/layout/grid7.xml
@@ -0,0 +1,71 @@
+<!-- Copyright (C) 2010 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.
+-->
+
+<GridLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="10dip" >
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingRight="5dip"
+            android:text="flabe" />
+
+        <Button android:id="@+id/initialActivity"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="bax" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="5dip" >
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingRight="5dip"
+            android:text="bar" />
+
+        <EditText android:id="@+id/numberOfEvents"
+            android:layout_marginLeft="2dip"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@android:drawable/editbox_background"
+            android:numeric="integer"
+            android:scrollHorizontally="true"
+            android:maxLines="1" />
+    </LinearLayout>
+
+    <Button android:id="@+id/start"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="10dip"
+        android:text="Foo" />
+
+</GridLayout>
+
+
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity0.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity0.java
new file mode 100644
index 0000000..660c1c3
--- /dev/null
+++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity0.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.layout;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Activity0 extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.grid0);
+    }
+}
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity1.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity1.java
new file mode 100644
index 0000000..e6645f5
--- /dev/null
+++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity1.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.layout;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.GridLayout;
+
+public class Activity1 extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(new GridLayout(getBaseContext()));
+    }
+}
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java
new file mode 100644
index 0000000..6359903
--- /dev/null
+++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.layout;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+
+import android.widget.*;
+
+import static android.text.InputType.TYPE_CLASS_TEXT;
+import static android.view.inputmethod.EditorInfo.*;
+import static android.widget.GridLayout.*;
+
+public class Activity2 extends Activity {
+
+    public static View create(Context context) {
+        GridLayout vg = new GridLayout(context);
+        vg.setUseDefaultMargins(true);
+
+        Group row1 = new Group(1, CENTER);
+        Group row2 = new Group(2, CENTER);
+        Group row3 = new Group(3, BASELINE);
+        Group row4 = new Group(4, BASELINE);
+        Group row5 = new Group(5, FILL);
+        Group row6 = new Group(6, CENTER);
+        Group row7 = new Group(7, CENTER);
+
+        Group col1a = new Group(1, 5, CENTER);
+        Group col1b = new Group(1, 5, LEFT);
+        Group col1c = new Group(1, RIGHT);
+        Group col2 =  new Group(2, LEFT);
+        Group col3 =  new Group(3, FILL);
+        Group col4 =  new Group(4, FILL);
+
+        {
+            TextView v = new TextView(context);
+            v.setTextSize(48);
+            v.setText("Email setup");
+            vg.addView(v, new LayoutParams(row1, col1a));
+        }
+
+        {
+            TextView v = new TextView(context);
+            v.setTextSize(20);
+            v.setText("You can configure email in just a few steps:");
+            vg.addView(v, new LayoutParams(row2, col1b));
+        }
+
+        {
+            TextView v = new TextView(context);
+            v.setText("Email address:");
+            vg.addView(v, new LayoutParams(row3, col1c));
+        }
+
+        {
+            EditText v = new EditText(context);
+            v.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
+            {
+                LayoutParams lp = new LayoutParams(row3, col2);
+                lp.width = (int) v.getPaint().measureText("Frederick.W.Flintstone@bedrock.com    ");
+                vg.addView(v, lp);
+            }
+        }
+
+        {
+            TextView v = new TextView(context);
+            v.setText("Password:");
+            vg.addView(v, new LayoutParams(row4, col1c));
+        }
+
+        {
+            TextView v = new EditText(context);
+            v.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD);
+            {
+                LayoutParams lp = new LayoutParams(row4, col2);
+                lp.width = (int) v.getPaint().measureText("************");
+                vg.addView(v, lp);
+            }
+        }
+
+        {
+            Space v = new Space(context);
+            {
+                LayoutParams lp = new LayoutParams(row5, col3);
+                lp.rowWeight = 1;
+                lp.columnWeight = 1;
+                vg.addView(v, lp);
+            }
+        }
+
+        {
+            Button v = new Button(context);
+            v.setText("Manual setup");
+            vg.addView(v, new LayoutParams(row6, col4));
+        }
+
+        {
+            Button v = new Button(context);
+            v.setText("Next");
+            vg.addView(v, new LayoutParams(row7, col4));
+        }
+
+        return vg;
+    }
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(create(getBaseContext()));
+    }
+
+}
\ No newline at end of file
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity3.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity3.java
new file mode 100644
index 0000000..fc0b382
--- /dev/null
+++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity3.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.layout;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Activity3 extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.grid3);
+    }
+}
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity4.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity4.java
new file mode 100644
index 0000000..444f87c
--- /dev/null
+++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity4.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 20101 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.layout;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Activity4 extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.grid4);
+    }
+}
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity5.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity5.java
new file mode 100644
index 0000000..d317526
--- /dev/null
+++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity5.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.layout;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Activity5 extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.grid5);
+    }
+}
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity6.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity6.java
new file mode 100644
index 0000000..9bc9f9f
--- /dev/null
+++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity6.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.layout;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Activity6 extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.grid6);
+    }
+}
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity7.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity7.java
new file mode 100644
index 0000000..6350381
--- /dev/null
+++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity7.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.layout;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Activity7 extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.grid7);
+    }
+}