Merge "SliceView better measuring / updated heights and alignment"
diff --git a/slices/view/src/androidTest/java/androidx/app/slice/render/SliceRenderer.java b/slices/view/src/androidTest/java/androidx/app/slice/render/SliceRenderer.java
index 056af32..dc9196e 100644
--- a/slices/view/src/androidTest/java/androidx/app/slice/render/SliceRenderer.java
+++ b/slices/view/src/androidTest/java/androidx/app/slice/render/SliceRenderer.java
@@ -65,7 +65,7 @@
             protected void onLayout(boolean changed, int l, int t, int r, int b) {
                 int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 900,
                         mContext.getResources().getDisplayMetrics());
-                int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300,
                         mContext.getResources().getDisplayMetrics());
                 mLayout.measure(makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
                         makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/GridContent.java b/slices/view/src/main/java/androidx/app/slice/widget/GridContent.java
index 41a9640..10a30ac 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/GridContent.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/GridContent.java
@@ -27,6 +27,9 @@
 import static android.app.slice.SliceItem.FORMAT_TEXT;
 import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
 
+import android.app.slice.Slice;
+import android.content.Context;
+import android.content.res.Resources;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
@@ -35,7 +38,9 @@
 import java.util.List;
 
 import androidx.app.slice.SliceItem;
+import androidx.app.slice.builders.GridBuilder;
 import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
 
 /**
  * Extracts information required to present content in a grid format from a slice.
@@ -50,9 +55,25 @@
     private ArrayList<CellContent> mGridContent = new ArrayList<>();
     private int mMaxCellLineCount;
     private boolean mHasImage;
+    private @GridBuilder.ImageMode int mLargestImageMode;
 
-    public GridContent(SliceItem gridItem) {
+    private int mBigPicMinHeight;
+    private int mBigPicMaxHeight;
+    private int mAllImagesHeight;
+    private int mImageTextHeight;
+    private int mMaxHeight;
+    private int mMinHeight;
+
+    public GridContent(Context context, SliceItem gridItem) {
         populate(gridItem);
+
+        Resources res = context.getResources();
+        mBigPicMinHeight = res.getDimensionPixelSize(R.dimen.abc_slice_big_pic_min_height);
+        mBigPicMaxHeight = res.getDimensionPixelSize(R.dimen.abc_slice_big_pic_max_height);
+        mAllImagesHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_only_height);
+        mImageTextHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_text_height);
+        mMinHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_min_height);
+        mMaxHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_max_height);
     }
 
     private void reset() {
@@ -60,12 +81,13 @@
         mMaxCellLineCount = 0;
         mHasImage = false;
         mGridContent.clear();
+        mLargestImageMode = 0;
     }
 
     /**
      * @return whether this grid has content that is valid to display.
      */
-    public boolean populate(SliceItem gridItem) {
+    private boolean populate(SliceItem gridItem) {
         reset();
         mColorItem = SliceQuery.findSubtype(gridItem, FORMAT_INT, SUBTYPE_COLOR);
         String[] hints = new String[] {HINT_SHORTCUT, HINT_TITLE};
@@ -99,6 +121,7 @@
             }
             mMaxCellLineCount = Math.max(mMaxCellLineCount, cc.getTextCount());
             mHasImage |= cc.hasImage();
+            mLargestImageMode = Math.max(mLargestImageMode, cc.getImageMode());
         }
     }
 
@@ -166,6 +189,37 @@
     }
 
     /**
+     * @return the height to display a grid row at when it is used as a small template.
+     */
+    public int getSmallHeight() {
+        return getHeight(true /* isSmall */);
+    }
+
+    /**
+     * @return the height the content in this template requires to be displayed.
+     */
+    public int getActualHeight() {
+        return getHeight(false /* isSmall */);
+    }
+
+    private int getHeight(boolean isSmall) {
+        if (!isValid()) {
+            return 0;
+        }
+        if (mAllImages) {
+            return mGridContent.size() == 1
+                    ? isSmall ? mBigPicMinHeight : mBigPicMaxHeight
+                    : mLargestImageMode == GridBuilder.ICON_IMAGE ? mMinHeight : mAllImagesHeight;
+        } else {
+            boolean twoLines = getMaxCellLineCount() > 1;
+            boolean hasImage = hasImage();
+            return (twoLines && !isSmall)
+                    ? hasImage ? mMaxHeight : mMinHeight
+                    : mLargestImageMode == GridBuilder.ICON_IMAGE ? mMinHeight : mImageTextHeight;
+        }
+    }
+
+    /**
      * Extracts information required to present content in a cell.
      * @hide
      */
@@ -175,6 +229,7 @@
         private ArrayList<SliceItem> mCellItems = new ArrayList<>();
         private int mTextCount;
         private boolean mHasImage;
+        private int mImageMode = -1;
 
         public CellContent(SliceItem cellItem) {
             populate(cellItem);
@@ -207,6 +262,13 @@
                         mTextCount++;
                         mCellItems.add(item);
                     } else if (imageCount < 1 && FORMAT_IMAGE.equals(item.getFormat())) {
+                        if (item.hasHint(Slice.HINT_NO_TINT)) {
+                            mImageMode = item.hasHint(Slice.HINT_LARGE)
+                                    ? GridBuilder.LARGE_IMAGE
+                                    : GridBuilder.SMALL_IMAGE;
+                        } else {
+                            mImageMode = GridBuilder.ICON_IMAGE;
+                        }
                         imageCount++;
                         mHasImage = true;
                         mCellItems.add(item);
@@ -269,5 +331,12 @@
         public boolean hasImage() {
             return mHasImage;
         }
+
+        /**
+         * @return the mode of the image.
+         */
+        public int getImageMode() {
+            return mImageMode;
+        }
     }
 }
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/GridRowView.java b/slices/view/src/main/java/androidx/app/slice/widget/GridRowView.java
index f267bdb..148730c 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/GridRowView.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/GridRowView.java
@@ -26,6 +26,8 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import static androidx.app.slice.widget.SliceView.MODE_SMALL;
+
 import android.annotation.TargetApi;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -67,15 +69,11 @@
 
     private static final String TAG = "GridView";
 
-    // TODO -- Should add notion to the builder so that apps could define the "see more" intent
-    private static final boolean ALLOW_SEE_MORE = false;
-
     private static final int TITLE_TEXT_LAYOUT = R.layout.abc_slice_title;
     private static final int TEXT_LAYOUT = R.layout.abc_slice_secondary_text;
-    // Max number of *just* images that can be shown in a row
-    private static final int MAX_IMAGES = 3;
+
     // Max number of normal cell items that can be shown in a row
-    private static final int MAX_ALL = 5;
+    private static final int MAX_CELLS = 5;
 
     // Max number of text items that can show in a cell
     private static final int MAX_CELL_TEXT = 2;
@@ -85,12 +83,10 @@
     private static final int MAX_CELL_IMAGES = 1;
 
     private int mRowIndex;
-    private boolean mIsAllImages;
-
+    private int mSmallImageSize;
     private int mIconSize;
-    private int mLargeIconSize;
-    private int mBigPictureHeight;
-    private int mAllImagesHeight;
+    private int mGutter;
+
     private GridContent mGridContent;
     private LinearLayout mViewContainer;
 
@@ -101,26 +97,31 @@
     public GridRowView(Context context, AttributeSet attrs) {
         super(context, attrs);
         final Resources res = getContext().getResources();
-        mIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_icon_size);
-        mLargeIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_large_icon_size);
-        mBigPictureHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_big_picture_height);
-        mAllImagesHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_only_height);
         mViewContainer = new LinearLayout(getContext());
         mViewContainer.setOrientation(LinearLayout.HORIZONTAL);
         addView(mViewContainer, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+        mViewContainer.setGravity(Gravity.CENTER_VERTICAL);
+        mIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_icon_size);
+        mSmallImageSize = res.getDimensionPixelSize(R.dimen.abc_slice_small_image_size);
+        mGutter = res.getDimensionPixelSize(R.dimen.abc_slice_grid_gutter);
+    }
+
+    @Override
+    public int getSmallHeight() {
+        // GridRow is small if its the first element in a list without a header presented in small
+        return mGridContent != null ? mGridContent.getSmallHeight() : 0;
+    }
+
+    @Override
+    public int getActualHeight() {
+        return mGridContent != null ? mGridContent.getActualHeight() : 0;
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (mIsAllImages) {
-            int count = getChildCount();
-            int height = (count == 1) ? mBigPictureHeight : mAllImagesHeight;
-            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
-            getLayoutParams().height = height;
-            for (int i = 0; i < count; i++) {
-                getChildAt(i).getLayoutParams().height = height;
-            }
-        }
+        int height = getMode() == MODE_SMALL ? getSmallHeight() : getActualHeight();
+        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+        mViewContainer.getLayoutParams().height = height;
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
@@ -128,20 +129,21 @@
     public void setTint(@ColorInt int tintColor) {
         super.setTint(tintColor);
         if (mGridContent != null) {
+            GridContent gc = mGridContent;
             // TODO -- could be smarter about this
             resetView();
-            populateViews(mGridContent);
+            populateViews(gc);
         }
     }
 
     /**
-     * This is called when GridView is the parent template.
+     * This is called when GridView is presented in small format.
      */
     @Override
     public void setSlice(Slice slice) {
         resetView();
         mRowIndex = 0;
-        mGridContent = new GridContent(slice.getItems().get(0));
+        mGridContent = new GridContent(getContext(), slice.getItems().get(0));
         populateViews(mGridContent);
     }
 
@@ -154,7 +156,7 @@
         resetView();
         setSliceActionListener(observer);
         mRowIndex = index;
-        mGridContent = new GridContent(slice);
+        mGridContent = new GridContent(getContext(), slice);
         populateViews(mGridContent);
     }
 
@@ -166,17 +168,13 @@
             mViewContainer.setTag(tagItem);
             makeClickable(mViewContainer);
         }
-        mIsAllImages = gc.isAllImages();
         ArrayList<GridContent.CellContent> cells = gc.getGridContent();
-        final int max = mIsAllImages ? MAX_IMAGES : MAX_ALL;
         for (int i = 0; i < cells.size(); i++) {
-            if (isFull()) {
+            if (mViewContainer.getChildCount() >= MAX_CELLS) {
+                // TODO -- use item if it exists
                 break;
             }
-            addCell(cells.get(i), i, Math.min(cells.size(), max));
-        }
-        if (ALLOW_SEE_MORE && mIsAllImages && cells.size() > getChildCount()) {
-            addSeeMoreCount(cells.size() - getChildCount());
+            addCell(cells.get(i), i, Math.min(cells.size(), MAX_CELLS));
         }
     }
 
@@ -199,15 +197,11 @@
         mViewContainer.addView(frame);
     }
 
-    private boolean isFull() {
-        return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
-    }
-
     /**
      * Adds a cell to the grid view based on the provided {@link SliceItem}.
      */
     private void addCell(GridContent.CellContent cell, int index, int total) {
-        final int maxCellText = getMode() == SliceView.MODE_SMALL
+        final int maxCellText = getMode() == MODE_SMALL
                 ? MAX_CELL_TEXT_SMALL
                 : MAX_CELL_TEXT;
         LinearLayout cellContainer = new LinearLayout(getContext());
@@ -223,7 +217,7 @@
         boolean singleItem = cellItems.size() == 1;
         List<SliceItem> textItems = null;
         // In small format we display one text item and prefer titles
-        if (!singleItem && getMode() == SliceView.MODE_SMALL) {
+        if (!singleItem && getMode() == MODE_SMALL) {
             // Get all our text items
             textItems = cellItems.stream().filter(new Predicate<SliceItem>() {
                 @Override
@@ -262,6 +256,12 @@
         if (added) {
             mViewContainer.addView(cellContainer,
                     new LinearLayout.LayoutParams(0, WRAP_CONTENT, 1));
+            if (index != total - 1) {
+                // If we're not the last or only element add space between items
+                MarginLayoutParams lp =
+                        (LinearLayout.MarginLayoutParams) cellContainer.getLayoutParams();
+                lp.setMarginEnd(mGutter);
+            }
             if (contentIntentItem != null) {
                 EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_BUTTON,
                         EventInfo.ROW_TYPE_GRID, mRowIndex);
@@ -295,15 +295,20 @@
         } else if (FORMAT_IMAGE.equals(format)) {
             ImageView iv = new ImageView(getContext());
             iv.setImageIcon(item.getIcon());
-            if (color != -1 && !item.hasHint(HINT_NO_TINT) && !item.hasHint(HINT_LARGE)) {
-                iv.setColorFilter(color);
-            }
-            int size = mIconSize;
+            LinearLayout.LayoutParams lp;
             if (item.hasHint(HINT_LARGE)) {
                 iv.setScaleType(ScaleType.CENTER_CROP);
-                size = singleItem ? MATCH_PARENT : mLargeIconSize;
+                lp = new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+            } else {
+                boolean isIcon = !item.hasHint(HINT_NO_TINT);
+                int size = isIcon ? mIconSize : mSmallImageSize;
+                iv.setScaleType(isIcon ? ScaleType.CENTER_INSIDE : ScaleType.CENTER_CROP);
+                lp = new LinearLayout.LayoutParams(size, size);
             }
-            container.addView(iv, new LayoutParams(size, size));
+            if (color != -1 && !item.hasHint(HINT_NO_TINT)) {
+                iv.setColorFilter(color);
+            }
+            container.addView(iv, lp);
             addedView = iv;
         }
         return addedView != null;
@@ -334,7 +339,6 @@
 
     @Override
     public void resetView() {
-        mIsAllImages = true;
         mViewContainer.removeAllViews();
     }
 }
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java b/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java
index 7aace75..bdd1ac5 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java
@@ -16,9 +16,6 @@
 
 package androidx.app.slice.widget;
 
-import static android.app.slice.Slice.HINT_PARTIAL;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.support.annotation.RestrictTo;
@@ -30,8 +27,6 @@
 
 import androidx.app.slice.Slice;
 import androidx.app.slice.SliceItem;
-import androidx.app.slice.core.SliceQuery;
-import androidx.app.slice.view.R;
 
 /**
  * @hide
@@ -42,7 +37,6 @@
 
     private final LargeSliceAdapter mAdapter;
     private final RecyclerView mRecyclerView;
-    private final int mDefaultHeight;
     private Slice mSlice;
     private boolean mIsScrollable;
     private ListContent mListContent;
@@ -54,7 +48,11 @@
         mAdapter = new LargeSliceAdapter(context);
         mRecyclerView.setAdapter(mAdapter);
         addView(mRecyclerView);
-        mDefaultHeight = getResources().getDimensionPixelSize(R.dimen.abc_slice_large_height);
+    }
+
+    @Override
+    public int getActualHeight() {
+        return mListContent != null ? mListContent.getListHeight() : 0;
     }
 
     @Override
@@ -82,20 +80,6 @@
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        if (mRecyclerView.getMeasuredHeight() > width
-                || (mSlice != null && SliceQuery.hasHints(mSlice, HINT_PARTIAL))) {
-            mRecyclerView.getLayoutParams().height = width;
-        } else {
-            mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
-        }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @Override
     public void setSlice(Slice slice) {
         mSlice = slice;
         populate();
@@ -111,7 +95,7 @@
         if (mSlice == null) {
             return;
         }
-        mListContent = new ListContent(mSlice);
+        mListContent = new ListContent(getContext(), mSlice);
         mAdapter.setSliceItems(mListContent.getRowItems(), mTintColor);
     }
 
@@ -128,6 +112,5 @@
         mSlice = null;
         mAdapter.setSliceItems(null, -1);
         mListContent = null;
-        mAdapter.setSliceItems(null, -1);
     }
 }
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/ListContent.java b/slices/view/src/main/java/androidx/app/slice/widget/ListContent.java
index 79bfa39..246ef0b 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/ListContent.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/ListContent.java
@@ -17,6 +17,7 @@
 package androidx.app.slice.widget;
 
 import static android.app.slice.Slice.HINT_ACTIONS;
+import static android.app.slice.Slice.HINT_HORIZONTAL;
 import static android.app.slice.Slice.HINT_LIST_ITEM;
 import static android.app.slice.Slice.HINT_SHORTCUT;
 import static android.app.slice.Slice.SUBTYPE_COLOR;
@@ -25,6 +26,7 @@
 import static android.app.slice.SliceItem.FORMAT_SLICE;
 import static android.app.slice.SliceItem.FORMAT_TEXT;
 
+import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
@@ -48,8 +50,10 @@
     private SliceItem mColorItem;
     private ArrayList<SliceItem> mRowItems = new ArrayList<>();
     private List<SliceItem> mSliceActions;
+    private Context mContext;
 
-    public ListContent(Slice slice) {
+    public ListContent(Context context, Slice slice) {
+        mContext = context;
         populate(slice);
     }
 
@@ -65,7 +69,7 @@
     /**
      * @return whether this row has content that is valid to display.
      */
-    public boolean populate(Slice slice) {
+    private boolean populate(Slice slice) {
         reset();
         mColorItem = SliceQuery.findSubtype(slice, FORMAT_INT, SUBTYPE_COLOR);
         // Find slice actions
@@ -98,6 +102,24 @@
     }
 
     /**
+     * @return the total height of all the rows contained in this list.
+     */
+    public int getListHeight() {
+        int height = 0;
+        for (int i = 0; i < mRowItems.size(); i++) {
+            SliceItem item = mRowItems.get(i);
+            if (item.hasHint(HINT_HORIZONTAL)) {
+                GridContent gc = new GridContent(mContext, item);
+                height += gc.getActualHeight();
+            } else {
+                RowContent rc = new RowContent(mContext, item, i == 0 /* isHeader */);
+                height += rc.getActualHeight();
+            }
+        }
+        return height;
+    }
+
+    /**
      * @return whether this list has content that is valid to display.
      */
     public boolean isValid() {
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/RowContent.java b/slices/view/src/main/java/androidx/app/slice/widget/RowContent.java
index 920dc52..a22a37d 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/RowContent.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/RowContent.java
@@ -30,8 +30,10 @@
 
 import static androidx.app.slice.core.SliceHints.SUBTYPE_RANGE;
 
+import android.content.Context;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -39,6 +41,7 @@
 
 import androidx.app.slice.SliceItem;
 import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
 
 /**
  * Extracts information required to present content in a row format from a slice.
@@ -57,9 +60,14 @@
     private boolean mEndItemsContainAction;
     private SliceItem mRange;
     private boolean mIsHeader;
+    private int mLineCount = 0;
+    private int mMaxHeight;
+    private int mMinHeight;
 
-    public RowContent(SliceItem rowSlice, boolean isHeader) {
+    public RowContent(Context context, SliceItem rowSlice, boolean isHeader) {
         populate(rowSlice, isHeader);
+        mMaxHeight = context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_max_height);
+        mMinHeight = context.getResources().getDimensionPixelSize(R.dimen.abc_slice_row_min_height);
     }
 
     /**
@@ -72,12 +80,13 @@
         mSubtitleItem = null;
         mEndItems.clear();
         mIsHeader = false;
+        mLineCount = 0;
     }
 
     /**
      * @return whether this row has content that is valid to display.
      */
-    public boolean populate(SliceItem rowSlice, boolean isHeader) {
+    private boolean populate(SliceItem rowSlice, boolean isHeader) {
         reset();
         mIsHeader = isHeader;
         if (!isValidRow(rowSlice)) {
@@ -131,6 +140,12 @@
                     endItems.add(item);
                 }
             }
+            if (hasText(mTitleItem)) {
+                mLineCount++;
+            }
+            if (hasText(mSubtitleItem)) {
+                mLineCount++;
+            }
             // Special rules for end items: only one timestamp, can't be mixture of icons / actions
             boolean hasTimestamp = mStartItem != null
                     && FORMAT_TIMESTAMP.equals(mStartItem.getFormat());
@@ -212,6 +227,33 @@
     }
 
     /**
+     * @return the number of lines of text contained in this row.
+     */
+    public int getLineCount() {
+        return mLineCount;
+    }
+
+    /**
+     * @return the height to display a row at when it is used as a small template.
+     */
+    public int getSmallHeight() {
+        return mMaxHeight;
+    }
+
+    /**
+     * @return the height the content in this template requires to be displayed.
+     */
+    public int getActualHeight() {
+        return isValid()
+                ? (getLineCount() > 1 || mIsHeader) ? mMaxHeight : mMinHeight
+                : 0;
+    }
+
+    private static boolean hasText(SliceItem textSlice) {
+        return textSlice != null && !TextUtils.isEmpty(textSlice.getText());
+    }
+
+    /**
      * @return whether this is a valid item to use to populate a row of content.
      */
     private static boolean isValidRow(SliceItem rowSlice) {
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/RowView.java b/slices/view/src/main/java/androidx/app/slice/widget/RowView.java
index 5b3e74c..bcd4f61 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/RowView.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/RowView.java
@@ -109,6 +109,19 @@
         mSeekBar = (SeekBar) findViewById(R.id.seek_bar);
         mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
     }
+
+
+    @Override
+    public int getSmallHeight() {
+        // RowView is in small format when it is the header of a list and displays at max height.
+        return mRowContent != null && mRowContent.isValid() ? mRowContent.getSmallHeight() : 0;
+    }
+
+    @Override
+    public int getActualHeight() {
+        return mRowContent != null && mRowContent.isValid() ? mRowContent.getActualHeight() : 0;
+    }
+
     @Override
     public void setTint(@ColorInt int tintColor) {
         super.setTint(tintColor);
@@ -126,6 +139,13 @@
         }
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int height = getMode() == MODE_SMALL ? getSmallHeight() : getActualHeight();
+        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
     /**
      * This is called when RowView is being used as a component in a large template.
      */
@@ -136,7 +156,7 @@
         mRowIndex = index;
         mIsHeader = isHeader;
         mHeaderActions = null;
-        mRowContent = new RowContent(slice, mIsHeader);
+        mRowContent = new RowContent(getContext(), slice, mIsHeader);
         populateViews();
     }
 
@@ -148,8 +168,8 @@
         mRowIndex = 0;
         mIsHeader = true;
         mHeaderActions = null;
-        ListContent lc = new ListContent(slice);
-        mRowContent = new RowContent(lc.getHeaderItem(), true /* isHeader */);
+        ListContent lc = new ListContent(getContext(), slice);
+        mRowContent = new RowContent(getContext(), lc.getHeaderItem(), true /* isHeader */);
         populateViews();
     }
 
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SliceChildView.java b/slices/view/src/main/java/androidx/app/slice/widget/SliceChildView.java
index 9a5279f..9768894 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/SliceChildView.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceChildView.java
@@ -75,6 +75,20 @@
     }
 
     /**
+     * @return the height of this view when displayed in {@link SliceView#MODE_SMALL}.
+     */
+    public int getSmallHeight() {
+        return 0;
+    }
+
+    /**
+     * @return the height of this view if it displayed all of its contents.
+     */
+    public int getActualHeight() {
+        return 0;
+    }
+
+    /**
      * @param slice the slice to show in this view.
      */
     public abstract void setSlice(Slice slice);
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java b/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
index 3648234..6e1f0c0 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
@@ -114,13 +114,7 @@
      */
     public static final int MODE_SHORTCUT    = 3;
 
-    /**
-     * Will select the type of slice binding based on size of the View. TODO: Put in some info about
-     * that selection.
-     */
-    private static final int MODE_AUTO = 0;
-
-    private int mMode = MODE_AUTO;
+    private int mMode = MODE_LARGE;
     private Slice mCurrentSlice;
     private SliceChildView mCurrentView;
     private List<SliceItem> mActions;
@@ -130,6 +124,8 @@
     private boolean mIsScrollable = true;
 
     private final int mShortcutSize;
+    private final int mMinLargeHeight;
+
     private AttributeSet mAttrs;
     private int mThemeTintColor = -1;
 
@@ -161,44 +157,73 @@
         mActionRow = new ActionRow(getContext(), true);
         mActionRow.setBackground(new ColorDrawable(0xffeeeeee));
         mCurrentView = new LargeTemplateView(getContext());
+        mCurrentView.setMode(getMode());
         addView(mCurrentView.getView(), getChildLp(mCurrentView.getView()));
         addView(mActionRow, getChildLp(mActionRow));
         mShortcutSize = getContext().getResources()
                 .getDimensionPixelSize(R.dimen.abc_slice_shortcut_size);
+        mMinLargeHeight = getResources().getDimensionPixelSize(R.dimen.abc_slice_large_height);
+    }
+
+    private int getHeightForMode() {
+        int mode = getMode();
+        if (mode == MODE_SHORTCUT) {
+            return mShortcutSize;
+        }
+        return mode == MODE_LARGE
+                ? mCurrentView.getActualHeight()
+                : mCurrentView.getSmallHeight();
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int childWidth = MeasureSpec.getSize(widthMeasureSpec);
-        int childHeight = MeasureSpec.getSize(heightMeasureSpec);
         if (MODE_SHORTCUT == mMode) {
-            // TODO: consider scaling the shortcut to fit
+            // TODO: consider scaling the shortcut to fit if too small
             childWidth = mShortcutSize;
             width = mShortcutSize;
         }
+
+        final int actionHeight = mActionRow.getVisibility() != View.GONE
+                ? mActionRow.getMeasuredHeight()
+                : 0;
+        final int sliceHeight = getHeightForMode() + actionHeight;
+        final int heightAvailable = MeasureSpec.getSize(heightMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int height = heightAvailable;
+        if (heightAvailable >= sliceHeight) {
+            // Available space is larger than the slice
+            if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
+                height = sliceHeight;
+            }
+        } else {
+            // Not enough space available for slice in current mode
+            if (getMode() == MODE_LARGE && heightAvailable >= mMinLargeHeight + actionHeight) {
+                // It's just a slice with scrolling content; cap it to height available.
+                height = heightAvailable;
+            } else if (getMode() == MODE_SHORTCUT) {
+                // TODO: consider scaling the shortcut to fit if too small
+                height = mShortcutSize;
+            }
+        }
+        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+        // Measure the children without the padding
         final int left = getPaddingLeft();
         final int top = getPaddingTop();
         final int right = getPaddingRight();
         final int bot = getPaddingBottom();
-
-        // Measure the children without the padding
+        int childHeight = MeasureSpec.getSize(heightMeasureSpec);
         childWidth -= left + right;
         childHeight -= top + bot;
         int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
         int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
         measureChildren(childWidthMeasureSpec, childHeightMeasureSpec);
 
-        // Figure out parent height
-        int actionHeight = mActionRow.getVisibility() != View.GONE
-                ? mActionRow.getMeasuredHeight()
-                : 0;
-        int currViewHeight = mCurrentView.getView().getMeasuredHeight() + top + bot;
-        int newHeightSpec = MeasureSpec.makeMeasureSpec(currViewHeight + actionHeight,
-                MeasureSpec.EXACTLY);
         // Figure out parent width
         width += left + right;
-        setMeasuredDimension(width, newHeightSpec);
+        setMeasuredDimension(width, heightMeasureSpec);
     }
 
     @Override
@@ -334,9 +359,6 @@
      * @return the mode this view is presenting in.
      */
     public @SliceMode int getMode() {
-        if (mMode == MODE_AUTO) {
-            return MODE_LARGE;
-        }
         return mMode;
     }
 
@@ -375,7 +397,7 @@
             mCurrentView.resetView();
             return;
         }
-        ListContent lc = new ListContent(mCurrentSlice);
+        ListContent lc = new ListContent(getContext(), mCurrentSlice);
         if (!lc.isValid()) {
             mCurrentView.resetView();
             mCurrentView.setVisibility(View.GONE);
@@ -397,7 +419,7 @@
             }
             addView(mCurrentView.getView(), getChildLp(mCurrentView.getView()));
             addView(mActionRow, getChildLp(mActionRow));
-            mCurrentView.setMode(mMode);
+            mCurrentView.setMode(mode);
         }
         // Scrolling
         if (mode == MODE_LARGE && (mCurrentView instanceof LargeTemplateView)) {
@@ -451,7 +473,8 @@
         if (child instanceof ShortcutView) {
             return new LayoutParams(mShortcutSize, mShortcutSize);
         } else {
-            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+            return new LayoutParams(LayoutParams.MATCH_PARENT,
+                    LayoutParams.MATCH_PARENT);
         }
     }
 
@@ -462,8 +485,6 @@
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     public static String modeToString(@SliceMode int mode) {
         switch(mode) {
-            case MODE_AUTO:
-                return "MODE AUTO";
             case MODE_SHORTCUT:
                 return "MODE SHORTCUT";
             case MODE_SMALL:
diff --git a/slices/view/src/main/res/layout-v21/abc_slice_small_template.xml b/slices/view/src/main/res/layout-v21/abc_slice_small_template.xml
deleted file mode 100644
index 7707dae..0000000
--- a/slices/view/src/main/res/layout-v21/abc_slice_small_template.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2017 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.
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/abc_slice_row_min_height"
-    android:maxHeight="@dimen/abc_slice_row_max_height"
-    android:gravity="center_vertical"
-    android:background="?android:attr/activatedBackgroundIndicator"
-    android:clipToPadding="false">
-
-    <LinearLayout
-        android:id="@+id/icon_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="start|center_vertical"
-        android:orientation="horizontal"
-        android:paddingEnd="12dp"
-        android:paddingTop="4dp"
-        android:paddingBottom="4dp"/>
-
-    <LinearLayout
-        android:id="@android:id/content"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:gravity="center_vertical"
-        android:orientation="vertical">
-
-        <TextView android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:maxLines="2"
-            android:textAppearance="?android:attr/textAppearanceListItem" />
-
-        <TextView android:id="@android:id/summary"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignStart="@android:id/title"
-            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
-            android:textColor="?android:attr/textColorSecondary"
-            android:maxLines="10" />
-
-        <SeekBar
-            android:id="@+id/seek_bar"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone" />
-
-        <ProgressBar
-            android:id="@+id/progress_bar"
-            style="?android:attr/progressBarStyleHorizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone" />
-
-    </LinearLayout>
-
-    <View
-        android:id="@+id/divider"
-        android:layout_width="1dp"
-        android:layout_height="match_parent"
-        android:layout_marginTop="8dp"
-        android:layout_marginBottom="8dp"
-        android:background="?android:attr/listDivider"
-        android:visibility="gone"/>
-
-    <LinearLayout android:id="@android:id/widget_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="end|center_vertical"
-        android:orientation="horizontal" />
-
-</LinearLayout>
diff --git a/slices/view/src/main/res/layout/abc_slice_grid.xml b/slices/view/src/main/res/layout/abc_slice_grid.xml
index 890f77d..e4cf7c5 100644
--- a/slices/view/src/main/res/layout/abc_slice_grid.xml
+++ b/slices/view/src/main/res/layout/abc_slice_grid.xml
@@ -17,8 +17,7 @@
 <androidx.app.slice.widget.GridRowView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/abc_slice_grid_image_only_height"
+    android:layout_height="match_parent"
     android:gravity="center_vertical"
     android:background="?android:attr/activatedBackgroundIndicator"
     android:clipToPadding="false">
diff --git a/slices/view/src/main/res/layout/abc_slice_secondary_text.xml b/slices/view/src/main/res/layout/abc_slice_secondary_text.xml
index b446ddd..0870465 100644
--- a/slices/view/src/main/res/layout/abc_slice_secondary_text.xml
+++ b/slices/view/src/main/res/layout/abc_slice_secondary_text.xml
@@ -23,4 +23,6 @@
         android:gravity="center"
         android:layout_height="wrap_content"
         android:padding="4dp"
-        android:layout_width="match_parent" />
+        android:layout_width="match_parent"
+        android:maxLines="1"
+        android:ellipsize="end"/>
diff --git a/slices/view/src/main/res/layout/abc_slice_small_template.xml b/slices/view/src/main/res/layout/abc_slice_small_template.xml
index 2d5e913..4a47c06 100644
--- a/slices/view/src/main/res/layout/abc_slice_small_template.xml
+++ b/slices/view/src/main/res/layout/abc_slice_small_template.xml
@@ -17,11 +17,11 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/abc_slice_row_min_height"
-    android:maxHeight="@dimen/abc_slice_row_max_height"
+    android:layout_height="match_parent"
     android:gravity="center_vertical"
+    android:layout_gravity="center_vertical"
     android:background="?android:attr/activatedBackgroundIndicator"
+    android:orientation="horizontal"
     android:clipToPadding="false">
 
     <LinearLayout
@@ -34,8 +34,8 @@
 
     <LinearLayout
         android:id="@android:id/content"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
         android:layout_weight="1"
         android:gravity="center_vertical"
         android:orientation="vertical">
@@ -43,13 +43,13 @@
         <TextView android:id="@android:id/title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:maxLines="2"/>
+            android:maxLines="1"/>
 
         <TextView android:id="@android:id/summary"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignStart="@android:id/title"
-            android:maxLines="10" />
+            android:maxLines="1" />
 
         <SeekBar
             android:id="@+id/seek_bar"
diff --git a/slices/view/src/main/res/layout/abc_slice_title.xml b/slices/view/src/main/res/layout/abc_slice_title.xml
index e1bdf03..70a1400 100644
--- a/slices/view/src/main/res/layout/abc_slice_title.xml
+++ b/slices/view/src/main/res/layout/abc_slice_title.xml
@@ -23,4 +23,6 @@
         android:gravity="center"
         android:layout_height="wrap_content"
         android:padding="4dp"
-        android:layout_width="match_parent" />
+        android:layout_width="match_parent"
+        android:maxLines="1"
+        android:ellipsize="end"/>
diff --git a/slices/view/src/main/res/values/dimens.xml b/slices/view/src/main/res/values/dimens.xml
index ff2fb97..04d74f3 100644
--- a/slices/view/src/main/res/values/dimens.xml
+++ b/slices/view/src/main/res/values/dimens.xml
@@ -17,17 +17,18 @@
 
 <resources>
     <!-- General -->
-    <!-- Size of normal icons / images in a slice -->
+    <!-- Size of icons in a slice -->
     <dimen name="abc_slice_icon_size">24dp</dimen>
-    <!-- Size of large icons / images in a slice -->
-    <dimen name="abc_slice_large_icon_size">48dp</dimen>
+    <!-- Size of small images in a slice -->
+    <dimen name="abc_slice_small_image_size">48dp</dimen>
     <!-- Standard padding used in a slice -->
     <dimen name="abc_slice_padding">16dp</dimen>
 
     <!-- Size of a slice shortcut view -->
     <dimen name="abc_slice_shortcut_size">56dp</dimen>
-
-    <!-- Height of a large template -->
+    <!-- Minimum height of a small template -->
+    <dimen name="abc_slice_small_height">48dp</dimen>
+    <!-- Minimum height of a large template -->
     <dimen name="abc_slice_large_height">240dp</dimen>
 
     <!-- Row view sizes-->
@@ -39,10 +40,22 @@
     <dimen name="abc_slice_row_active_input_height">120dp</dimen>
 
     <!-- Grid view sizes-->
-    <!-- Height of a grid row displaying only images -->
+    <!-- Height of a grid row displaying only text or only small images (but not both) -->
+    <dimen name="abc_slice_grid_min_height">60dp</dimen>
+    <!-- Height of a grid row displaying only large images -->
     <dimen name="abc_slice_grid_image_only_height">86dp</dimen>
-    <!-- Height of a grid row showing text and images -->
-    <dimen name="abc_slice_grid_height">120dp</dimen>
-    <!-- Height of expanded grid row if showing a single large image -->
-    <dimen name="abc_slice_grid_big_picture_height">180dp</dimen>
+    <!-- Height of a grid row showing one text item along with a large image -->
+    <dimen name="abc_slice_grid_image_text_height">120dp</dimen>
+    <!-- Height of a grid row showing two text items along with a large image -->
+    <dimen name="abc_slice_grid_max_height">140dp</dimen>
+    <!-- Height of a grid row showing 1-2 text items along with a small image -->
+    <dimen name="abc_slice_grid_small_image_text_height">120dp</dimen>
+    <!-- Gutter between cells in a grid row-->
+    <dimen name="abc_slice_grid_gutter">4dp</dimen>
+
+    <!-- Big picture -->
+    <!-- Min height of row showing a single large image -->
+    <dimen name="abc_slice_big_pic_min_height">120dp</dimen>
+    <!-- Max height of row showing a single large image -->
+    <dimen name="abc_slice_big_pic_max_height">140dp</dimen>
 </resources>
\ No newline at end of file