Merge "Update view side for small template / header changes"
diff --git a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java
index 2e526e0..49818e6 100644
--- a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java
+++ b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java
@@ -32,6 +32,8 @@
 import android.text.format.DateUtils;
 import android.text.style.ForegroundColorSpan;
 
+import java.util.Calendar;
+
 import androidx.app.slice.Slice;
 import androidx.app.slice.SliceProvider;
 import androidx.app.slice.builders.GridBuilder;
@@ -167,12 +169,25 @@
     }
 
     private Slice createContact(Uri sliceUri) {
+        final long lastCalled = System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS;
+        CharSequence lastCalledString = DateUtils.getRelativeTimeSpanString(lastCalled,
+                Calendar.getInstance().getTimeInMillis(),
+                DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
         return new ListBuilder(getContext(), sliceUri)
                 .setColor(0xff3949ab)
-                .addRow(b -> b
+                .setHeader(b -> b
                         .setTitle("Mady Pitza")
-                        .setSubtitle("Frequently contacted contact")
-                        .addEndItem(Icon.createWithResource(getContext(), R.drawable.mady)))
+                        .setSummarySubtitle("Called " + lastCalledString)
+                        .setContentIntent(getBroadcastIntent(ACTION_TOAST, "See contact info")))
+                .addRow(b -> b
+                        .setTitleItem(Icon.createWithResource(getContext(), R.drawable.ic_call))
+                        .setTitle("314-259-2653")
+                        .setSubtitle("Call lasted 1 hr 17 min")
+                        .addEndItem(lastCalled))
+                .addRow(b -> b
+                        .setTitleItem(Icon.createWithResource(getContext(), R.drawable.ic_text))
+                        .setTitle("You: Coooooool see you then")
+                        .addEndItem(System.currentTimeMillis() - 40 * DateUtils.MINUTE_IN_MILLIS))
                 .addGrid(b -> b
                         .addCell(cb -> cb
                             .addImage(Icon.createWithResource(getContext(), R.drawable.ic_call))
@@ -263,6 +278,11 @@
 
         return new ListBuilder(getContext(), sliceUri)
                 .setColor(0xff0F9D58)
+                .setHeader(b -> b
+                    .setTitle("Get ride")
+                    .setSubtitle(headerSubtitle)
+                    .setSummarySubtitle("Ride to work in 12 min | Ride home in 1 hour 45 min")
+                    .setContentIntent(getBroadcastIntent(ACTION_TOAST, "get ride")))
                 .addRow(b -> b
                     .setTitle("Work")
                     .setSubtitle(workSubtitle)
@@ -270,7 +290,7 @@
                             getBroadcastIntent(ACTION_TOAST, "work")))
                 .addRow(b -> b
                     .setTitle("Home")
-                    .setSubtitle("2 hours 33 min via 101")
+                    .setSubtitle(homeSubtitle)
                     .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_home),
                             getBroadcastIntent(ACTION_TOAST, "home")))
                 .build();
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilderV1Impl.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilderV1Impl.java
index 475be81..1e8b4e8 100644
--- a/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilderV1Impl.java
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilderV1Impl.java
@@ -288,7 +288,7 @@
         @NonNull
         @Override
         public void setTitleItem(long timeStamp) {
-            mStartItem = new SliceItem(timeStamp, FORMAT_TIMESTAMP, null, new String[0]);
+            mStartItem = new SliceItem(timeStamp, FORMAT_TIMESTAMP, null, new String[]{HINT_TITLE});
         }
 
         /**
@@ -303,7 +303,7 @@
          */
         @Override
         public void setTitleItem(@Nullable Icon icon, boolean isLoading) {
-            mStartItem = new SliceItem(icon, FORMAT_IMAGE, null, new String[0]);
+            mStartItem = new SliceItem(icon, FORMAT_IMAGE, null, new String[]{HINT_TITLE});
             if (isLoading) {
                 mStartItem.addHint(HINT_PARTIAL);
             }
@@ -322,7 +322,8 @@
         @Override
         public void setTitleItem(Icon icon, PendingIntent action, boolean isLoading) {
             Slice actionSlice = new Slice.Builder(getBuilder()).addIcon(icon, null).build();
-            mStartItem = new SliceItem(action, actionSlice, FORMAT_ACTION, null, new String[0]);
+            mStartItem = new SliceItem(action, actionSlice, FORMAT_ACTION, null,
+                    new String[]{HINT_TITLE});
             if (isLoading) {
                 mStartItem.addHint(HINT_PARTIAL);
             }
@@ -519,7 +520,6 @@
             if (mContentIntent != null) {
                 wrapped.addAction(mContentIntent, b.build(), null /* subtype */);
             }
-            wrapped.addHints(HINT_TITLE);
         }
 
         /**
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 5b29ab2..d74230c 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,13 +17,12 @@
 package androidx.app.slice.widget;
 
 import static android.app.slice.Slice.HINT_ACTIONS;
-import static android.app.slice.Slice.HINT_LIST;
 import static android.app.slice.Slice.HINT_LIST_ITEM;
-import static android.app.slice.Slice.HINT_SUMMARY;
 import static android.app.slice.Slice.SUBTYPE_COLOR;
 import static android.app.slice.SliceItem.FORMAT_ACTION;
 import static android.app.slice.SliceItem.FORMAT_INT;
 import static android.app.slice.SliceItem.FORMAT_SLICE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
 
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -43,10 +42,10 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class ListContent {
 
+    private SliceItem mHeaderItem;
     private SliceItem mColorItem;
-    private SliceItem mSummaryItem;
     private ArrayList<SliceItem> mRowItems = new ArrayList<>();
-    private boolean mHasHeader;
+    private List<SliceItem> mSliceActions;
 
     public ListContent(Slice slice) {
         populate(slice);
@@ -57,9 +56,8 @@
      */
     public void reset() {
         mColorItem = null;
-        mSummaryItem = null;
+        mHeaderItem = null;
         mRowItems.clear();
-        mHasHeader = false;
     }
 
     /**
@@ -68,24 +66,36 @@
     public boolean populate(Slice slice) {
         reset();
         mColorItem = SliceQuery.findSubtype(slice, FORMAT_INT, SUBTYPE_COLOR);
-        // Find summary
-        SliceItem summaryItem = getSummaryItem(slice);
-        mSummaryItem = summaryItem;
+        // Find slice actions
+        SliceItem actionGroup = SliceQuery.find(slice, FORMAT_SLICE, HINT_ACTIONS, null);
+        if (actionGroup != null) {
+            // TODO: actually use the actions
+            mSliceActions = SliceQuery.findAll(actionGroup, FORMAT_ACTION, HINT_ACTIONS, null);
+        }
+        // Find header
+        mHeaderItem = findHeaderItem(slice);
+        if (mHeaderItem != null) {
+            mRowItems.add(mHeaderItem);
+        }
         // Filter + create row items
         List<SliceItem> children = slice.getItems();
         for (int i = 0; i < children.size(); i++) {
             final SliceItem child = children.get(i);
             final String format = child.getFormat();
-            if (!child.hasAnyHints(HINT_SUMMARY, HINT_ACTIONS)
+            if (!child.hasAnyHints(HINT_ACTIONS)
                     && (FORMAT_ACTION.equals(format) || FORMAT_SLICE.equals(format))) {
-                if (!mHasHeader && !child.hasHint(HINT_LIST_ITEM)) {
-                    mHasHeader = true;
+                if (mHeaderItem == null && !child.hasHint(HINT_LIST_ITEM)) {
+                    mHeaderItem = child;
                     mRowItems.add(0, child);
-                } else {
+                } else if (child.hasHint(HINT_LIST_ITEM)) {
                     mRowItems.add(child);
                 }
             }
         }
+        // Ensure we have something for the header -- use first row
+        if (mHeaderItem == null && mRowItems.size() >= 1) {
+            mHeaderItem = mRowItems.get(0);
+        }
         return isValid();
     }
 
@@ -93,8 +103,7 @@
      * @return whether this list has content that is valid to display.
      */
     public boolean isValid() {
-        return mSummaryItem != null
-                || mRowItems.size() > 0;
+        return mRowItems.size() > 0;
     }
 
     @Nullable
@@ -103,8 +112,13 @@
     }
 
     @Nullable
-    public SliceItem getSummaryItem() {
-        return mSummaryItem;
+    public SliceItem getHeaderItem() {
+        return mHeaderItem;
+    }
+
+    @Nullable
+    public List<SliceItem> getSliceActions() {
+        return mSliceActions;
     }
 
     public ArrayList<SliceItem> getRowItems() {
@@ -112,76 +126,28 @@
     }
 
     /**
-     * @return whether this list has a header or not.
+     * @return whether this list has an explicit header (i.e. row item without HINT_LIST_ITEM)
      */
     public boolean hasHeader() {
-        return mHasHeader;
+        return mHeaderItem != null && isValidHeader(mHeaderItem);
     }
 
-    /**
-     * @return A slice item of format slice that is hinted to be shown when the slice is in small
-     * format, or is the best option if nothing is appropriately hinted.
-     */
-    private static SliceItem getSummaryItem(@NonNull Slice slice) {
-        List<SliceItem> items = slice.getItems();
-        // See if a summary is specified
-        SliceItem summary = SliceQuery.find(slice, FORMAT_SLICE, HINT_SUMMARY, null);
-        if (summary != null) {
-            return summary;
+    @Nullable
+    private static SliceItem findHeaderItem(@NonNull Slice slice) {
+        // See if header is specified
+        SliceItem header = SliceQuery.find(slice, FORMAT_SLICE, null, HINT_LIST_ITEM);
+        if (header != null && isValidHeader(header)) {
+            return header;
         }
-        // Otherwise use the first non-color item and use it if it's a slice
-        SliceItem firstSlice = null;
-        for (int i = 0; i < items.size(); i++) {
-            if (!FORMAT_INT.equals(items.get(i).getFormat())) {
-                firstSlice = items.get(i);
-                break;
-            }
-        }
-        if (firstSlice != null && FORMAT_SLICE.equals(firstSlice.getFormat())) {
-            // Check if this slice is appropriate to use to populate small template
-            if (firstSlice.hasHint(HINT_LIST)) {
-                // Check for header, use that if it exists
-                SliceItem listHeader = SliceQuery.find(firstSlice, FORMAT_SLICE,
-                        null,
-                        new String[] {
-                                HINT_LIST_ITEM, HINT_LIST
-                        });
-                if (listHeader != null) {
-                    return findFirstSlice(listHeader);
-                } else {
-                    // Otherwise use the first list item
-                    SliceItem newFirst = firstSlice.getSlice().getItems().get(0);
-                    return findFirstSlice(newFirst);
-                }
-            } else {
-                // Not a list, find first slice with non-slice children
-                return findFirstSlice(firstSlice);
-            }
-        }
-        // Fallback, just use this and convert to SliceItem type slice
-        Slice.Builder sb = new Slice.Builder(slice.getUri());
-        Slice s = sb.addSubSlice(slice).build();
-        return s.getItems().get(0);
+        return null;
     }
 
-    /**
-     * @return Finds the first slice that has non-slice children.
-     */
-    private static SliceItem findFirstSlice(SliceItem slice) {
-        if (!FORMAT_SLICE.equals(slice.getFormat())) {
-            return slice;
+    private static boolean isValidHeader(SliceItem sliceItem) {
+        if (FORMAT_SLICE.equals(sliceItem.getFormat()) && !sliceItem.hasHint(HINT_LIST_ITEM)) {
+             // Minimum valid header is a slice with text
+            SliceItem item = SliceQuery.find(sliceItem, FORMAT_TEXT, (String) null, null);
+            return item != null;
         }
-        List<SliceItem> items = slice.getSlice().getItems();
-        for (int i = 0; i < items.size(); i++) {
-            if (FORMAT_SLICE.equals(items.get(i).getFormat())) {
-                SliceItem childSlice = items.get(i);
-                return findFirstSlice(childSlice);
-            } else {
-                // Doesn't have slice children so return it
-                return slice;
-            }
-        }
-        // Slices all the way down, just return it
-        return slice;
+        return false;
     }
 }
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 2d6954e..735db8e 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
@@ -16,6 +16,7 @@
 
 package androidx.app.slice.widget;
 
+import static android.app.slice.Slice.HINT_SUMMARY;
 import static android.app.slice.Slice.HINT_TITLE;
 import static android.app.slice.SliceItem.FORMAT_ACTION;
 import static android.app.slice.SliceItem.FORMAT_IMAGE;
@@ -48,12 +49,14 @@
     private SliceItem mStartItem;
     private SliceItem mTitleItem;
     private SliceItem mSubtitleItem;
+    private SliceItem mSummaryItem;
     private ArrayList<SliceItem> mEndItems = new ArrayList<>();
     private boolean mEndItemsContainAction;
     private SliceItem mRange;
+    private boolean mIsHeader;
 
-    public RowContent(SliceItem rowSlice, boolean showStartItem) {
-        populate(rowSlice, showStartItem);
+    public RowContent(SliceItem rowSlice, boolean isHeader) {
+        populate(rowSlice, isHeader);
     }
 
     /**
@@ -65,13 +68,15 @@
         mTitleItem = null;
         mSubtitleItem = null;
         mEndItems.clear();
+        mIsHeader = false;
     }
 
     /**
      * @return whether this row has content that is valid to display.
      */
-    public boolean populate(SliceItem rowSlice, boolean showStartItem) {
+    public boolean populate(SliceItem rowSlice, boolean isHeader) {
         reset();
+        mIsHeader = isHeader;
         if (!isValidRow(rowSlice)) {
             Log.w(TAG, "Provided SliceItem is invalid for RowContent");
             return false;
@@ -96,9 +101,7 @@
         if (rowItems.size() > 0) {
             // Start item
             if (isStartType(rowItems.get(0))) {
-                if (showStartItem) {
-                    mStartItem = rowItems.get(0);
-                }
+                mStartItem = rowItems.get(0);
                 rowItems.remove(0);
             }
             // Text + end items
@@ -107,10 +110,12 @@
                 final SliceItem item = rowItems.get(i);
                 if (FORMAT_TEXT.equals(item.getFormat())) {
                     if ((mTitleItem == null || !mTitleItem.hasHint(HINT_TITLE))
-                            && item.hasHint(HINT_TITLE)) {
+                            && item.hasHint(HINT_TITLE) && !item.hasHint(HINT_SUMMARY)) {
                         mTitleItem = item;
-                    } else if (mSubtitleItem == null) {
+                    } else if (mSubtitleItem == null && !item.hasHint(HINT_SUMMARY)) {
                         mSubtitleItem = item;
+                    } else if (mSummaryItem == null && item.hasHint(HINT_SUMMARY)) {
+                        mSummaryItem = item;
                     }
                 } else {
                     endItems.add(item);
@@ -164,7 +169,7 @@
 
     @Nullable
     public SliceItem getStartItem() {
-        return mStartItem;
+        return mIsHeader ? null : mStartItem;
     }
 
     @Nullable
@@ -177,6 +182,11 @@
         return mSubtitleItem;
     }
 
+    @Nullable
+    public SliceItem getSummaryItem() {
+        return mSummaryItem == null ? mSubtitleItem : mSummaryItem;
+    }
+
     public ArrayList<SliceItem> getEndItems() {
         return mEndItems;
     }
@@ -192,6 +202,9 @@
      * @return whether this is a valid item to use to populate a row of content.
      */
     private static boolean isValidRow(SliceItem rowSlice) {
+        if (rowSlice == null) {
+            return false;
+        }
         // Must be slice or action
         if (FORMAT_SLICE.equals(rowSlice.getFormat())
                 || FORMAT_ACTION.equals(rowSlice.getFormat())) {
@@ -237,8 +250,9 @@
      */
     private static boolean isStartType(SliceItem item) {
         final String type = item.getFormat();
-        return (FORMAT_ACTION.equals(type) && (SliceQuery.find(item, FORMAT_IMAGE) != null))
-                || FORMAT_IMAGE.equals(type)
-                || FORMAT_TIMESTAMP.equals(type);
+        return item.hasHint(HINT_TITLE)
+                && ((FORMAT_ACTION.equals(type) && (SliceQuery.find(item, FORMAT_IMAGE) != null))
+                    || FORMAT_IMAGE.equals(type)
+                    || FORMAT_TIMESTAMP.equals(type));
     }
 }
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 f46354c..5956317 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
@@ -134,7 +134,7 @@
         mInSmallMode = false;
         mRowIndex = index;
         mIsHeader = isHeader;
-        mRowContent = new RowContent(slice, !mIsHeader /* showStartItem */);
+        mRowContent = new RowContent(slice, mIsHeader);
         populateViews();
     }
 
@@ -147,7 +147,7 @@
         mRowIndex = 0;
         mIsHeader = true;
         ListContent lc = new ListContent(slice);
-        mRowContent = new RowContent(lc.getSummaryItem(), false /* showStartItem */);
+        mRowContent = new RowContent(lc.getHeaderItem(), true /* isHeader */);
         populateViews();
     }
 
@@ -174,7 +174,9 @@
         mPrimaryText.setTextColor(mTitleColor);
         mPrimaryText.setVisibility(titleItem != null ? View.VISIBLE : View.GONE);
 
-        final SliceItem subTitle = mRowContent.getSubtitleItem();
+        final SliceItem subTitle = mInSmallMode
+                ? mRowContent.getSummaryItem()
+                : mRowContent.getSubtitleItem();
         if (subTitle != null) {
             mSecondaryText.setText(subTitle.getText());
         }