Merge "HeaderBuilder should have overloaded methods for loading" into pi-preview1-androidx-dev
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 a1bd996..0901724 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
@@ -34,6 +34,7 @@
 import android.text.SpannableString;
 import android.text.format.DateUtils;
 import android.text.style.ForegroundColorSpan;
+import android.util.SparseArray;
 
 import java.util.Calendar;
 
@@ -60,11 +61,9 @@
     public static final String ACTION_TOAST_RANGE_VALUE =
             "com.example.androidx.slice.action.TOAST_RANGE_VALUE";
 
-    public static final int LOADING_DELAY_MS = 4000;
-
     public static final String[] URI_PATHS = {"message", "wifi", "note", "ride", "toggle",
-            "toggle2", "contact", "gallery", "weather", "reservation", "loadlist", "loadlist2",
-            "loadgrid", "loadgrid2", "inputrange", "range", "contact2", "subscription"};
+            "toggle2", "contact", "gallery", "weather", "reservation", "loadlist", "loadgrid",
+            "inputrange", "range", "contact2", "subscription"};
 
     /**
      * @return Uri with the provided path
@@ -91,6 +90,14 @@
     @Override
     public Slice onBindSlice(Uri sliceUri) {
         String path = sliceUri.getPath();
+        if (!path.equals("/loadlist")) {
+            mListSummaries.clear();
+            mListLastUpdate = 0;
+        }
+        if (!path.equals("/loadgrid")) {
+            mGridSummaries.clear();
+            mGridLastUpdate = 0;
+        }
         switch (path) {
             // TODO: add list / grid slices with 'see more' options
             case "/message":
@@ -116,13 +123,9 @@
             case "/reservation":
                 return createReservationSlice(sliceUri);
             case "/loadlist":
-                return createLoadingSlice(sliceUri, false /* loadAll */, true /* isList */);
-            case "/loadlist2":
-                return createLoadingSlice(sliceUri, true /* loadAll */, true /* isList */);
+                return createLoadingListSlice(sliceUri);
             case "/loadgrid":
-                return createLoadingSlice(sliceUri, false /* loadAll */, false /* isList */);
-            case "/loadgrid2":
-                return createLoadingSlice(sliceUri, true /* loadAll */, false /* isList */);
+                return createLoadingGridSlice(sliceUri);
             case "/inputrange":
                 return createStarRatingInputRange(sliceUri);
             case "/range":
@@ -545,90 +548,84 @@
     }
 
     private Handler mHandler = new Handler();
-    private Runnable mLoader;
-    private boolean mLoaded = false;
+    private SparseArray<String> mListSummaries = new SparseArray<>();
+    private long mListLastUpdate;
+    private SparseArray<String> mGridSummaries = new SparseArray<>();
+    private long mGridLastUpdate;
 
-    private Slice createLoadingSlice(Uri sliceUri, boolean loadAll, boolean isList) {
-        if (!mLoaded || mLoader != null) {
-            // Need to load content or we're still loading so just return partial
-            if (!mLoaded) {
-                mLoader = () -> {
-                    // Note that we've loaded things
-                    mLoader = null;
-                    mLoaded = true;
-                    // Notify to update the slice
-                    getContext().getContentResolver().notifyChange(sliceUri, null);
-                };
-                mHandler.postDelayed(mLoader, LOADING_DELAY_MS);
-            }
-            if (loadAll) {
-                return new ListBuilder(getContext(), sliceUri).build();
-            }
-            return createPartialSlice(sliceUri, true, isList);
-        } else {
-            mLoaded = false;
-            return createPartialSlice(sliceUri, false, isList);
+    private void update(long delay, SparseArray<String> summaries, int id, String s, Uri uri,
+            Runnable r) {
+        mHandler.postDelayed(() -> {
+            summaries.put(id, s);
+            getContext().getContentResolver().notifyChange(uri, null);
+            r.run();
+        }, delay);
+    }
+
+    private Slice createLoadingListSlice(Uri sliceUri) {
+        boolean updating = mListLastUpdate == 0
+                || mListLastUpdate < (System.currentTimeMillis() - 10 * System.currentTimeMillis());
+        if (updating) {
+            Runnable r = () -> mListLastUpdate = System.currentTimeMillis();
+            update(1000, mListSummaries, 0, "44 miles | 1 hour 45 min | $31.41", sliceUri, r);
+            update(1500, mListSummaries, 1, "12 miles | 12 min | $9.00", sliceUri, r);
+            update(1700, mListSummaries, 2, "5 miles | 10 min | $8.00", sliceUri, r);
         }
+        Slice s = new ListBuilder(getContext(), sliceUri)
+                .addRow(b -> b
+                        .setTitle("Work")
+                        .setSubtitle(mListSummaries.get(0, ""), updating)
+                        .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_work),
+                                ICON_IMAGE))
+                .addRow(b -> b
+                        .setTitle("Home")
+                        .setSubtitle(mListSummaries.get(1, ""), updating)
+                        .addEndItem(
+                                Icon.createWithResource(getContext(), R.drawable.ic_home),
+                                ICON_IMAGE))
+                .addRow(b -> b
+                        .setTitle("School")
+                        .setSubtitle(mListSummaries.get(2, ""), updating)
+                        .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_school),
+                                ICON_IMAGE))
+                .build();
+        return s;
     }
 
-    private Slice createPartialSlice(Uri sliceUri, boolean isPartial, boolean isList) {
-        Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_star_on);
-        PendingIntent intent = getBroadcastIntent(ACTION_TOAST, "star tapped");
-        PendingIntent intent2 = getBroadcastIntent(ACTION_TOAST, "toggle tapped");
-        if (isPartial) {
-            if (isList) {
-                return new ListBuilder(getContext(), sliceUri)
-                        .addRow(b -> createRow(b, "Slice that has content to load",
-                                "Temporary subtitle", icon, intent, true))
-                        .addRow(b -> createRow(b, null, null, null, intent, true))
-                        .addRow(b -> b
-                                .setTitle("My title")
-                                .addEndItem(new SliceAction(intent2, "Some action",
-                                        false /* isChecked */),
-                                        true /* isLoading */))
-                        .build();
-            } else {
-                return new ListBuilder(getContext(), sliceUri).addGrid(gb -> gb
-                        .addCell(b -> createCell(b, null, null, null, true))
-                        .addCell(b -> createCell(b, "Two stars", null, icon, true))
-                        .addCell(b -> createCell(b, null, null, null, true)))
-                        .build();
-            }
-        } else {
-            if (isList) {
-                return new ListBuilder(getContext(), sliceUri)
-                        .addRow(b -> createRow(b, "Slice that has content to load",
-                                "Subtitle loaded", icon, intent, false))
-                        .addRow(b -> createRow(b, "Loaded row", "Loaded subtitle",
-                                icon, intent, false))
-                        .addRow(b -> b
-                                .setTitle("My title")
-                                .addEndItem(new SliceAction(intent2, "Some action",
-                                                false /* isChecked */)))
-                        .build();
-            } else {
-                return new ListBuilder(getContext(), sliceUri).addGrid(gb -> gb
-                        .addCell(b -> createCell(b, "One star", "meh", icon, false))
-                        .addCell(b -> createCell(b, "Two stars", "good", icon, false))
-                        .addCell(b -> createCell(b, "Three stars", "best", icon, false)))
-                        .build();
-            }
+    private Slice createLoadingGridSlice(Uri sliceUri) {
+        boolean updating = mGridLastUpdate == 0
+                || mGridLastUpdate < (System.currentTimeMillis() - 10 * System.currentTimeMillis());
+        if (updating) {
+            Runnable r = () -> mGridLastUpdate = System.currentTimeMillis();
+            update(2000, mGridSummaries, 0, "Heavy traffic in your area", sliceUri, r);
+            update(3500, mGridSummaries, 1, "Typical conditions with delays up to 28 min",
+                    sliceUri, r);
+            update(3000, mGridSummaries, 2, "41 min", sliceUri, r);
+            update(1500, mGridSummaries, 3, "33 min", sliceUri, r);
+            update(1000, mGridSummaries, 4, "12 min", sliceUri, r);
         }
-    }
-
-    private ListBuilder.RowBuilder createRow(ListBuilder.RowBuilder rb, String title,
-            String subtitle, Icon icon, PendingIntent content, boolean isLoading) {
-        SliceAction primaryAction = new SliceAction(content, icon, title);
-        return rb.setTitle(title, isLoading)
-          .setSubtitle(subtitle, isLoading)
-          .addEndItem(icon, isLoading)
-          .setPrimaryAction(primaryAction);
-    }
-
-    private GridBuilder.CellBuilder createCell(GridBuilder.CellBuilder cb, String text1,
-            String text2, Icon icon, boolean isLoading) {
-        return cb.addText(text1, isLoading).addText(text2, isLoading).addImage(icon,
-                SMALL_IMAGE, isLoading);
+        Slice s = new ListBuilder(getContext(), sliceUri)
+                .setHeader(hb -> hb
+                        .setTitle(mGridSummaries.get(0, ""), updating)
+                        .setSubtitle(mGridSummaries.get(1, ""), updating))
+                .addGrid(gb -> gb
+                    .addCell(cb -> cb
+                            .addImage(Icon.createWithResource(getContext(), R.drawable.ic_home),
+                                    ICON_IMAGE)
+                            .addTitleText("Home")
+                            .addText(mGridSummaries.get(2, ""), updating))
+                    .addCell(cb -> cb
+                            .addImage(Icon.createWithResource(getContext(), R.drawable.ic_work),
+                                    ICON_IMAGE)
+                            .addTitleText("Work")
+                            .addText(mGridSummaries.get(3, ""), updating))
+                    .addCell(cb -> cb
+                            .addImage(Icon.createWithResource(getContext(), R.drawable.ic_school),
+                                    ICON_IMAGE)
+                            .addTitleText("School")
+                            .addText(mGridSummaries.get(4, ""), updating)))
+                    .build();
+        return s;
     }
 
     private PendingIntent getIntent(String action) {
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_school.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_school.xml
new file mode 100644
index 0000000..139d6a4
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_school.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright 2018 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M5,13.18v4L12,21l7,-3.82v-4L12,17l-7,-3.82zM12,3L1,9l11,6 9,-4.91V17h2V9L12,3z"/>
+</vector>
diff --git a/slices/builders/api/current.txt b/slices/builders/api/current.txt
index 89275b9..cc12c11 100644
--- a/slices/builders/api/current.txt
+++ b/slices/builders/api/current.txt
@@ -57,8 +57,11 @@
     method public androidx.slice.builders.ListBuilder.HeaderBuilder setContentDescription(java.lang.CharSequence);
     method public androidx.slice.builders.ListBuilder.HeaderBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
     method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(java.lang.CharSequence);
+    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(java.lang.CharSequence, boolean);
     method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummarySubtitle(java.lang.CharSequence);
+    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummarySubtitle(java.lang.CharSequence, boolean);
     method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(java.lang.CharSequence);
+    method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(java.lang.CharSequence, boolean);
   }
 
   public static class ListBuilder.InputRangeBuilder extends androidx.slice.builders.TemplateSliceBuilder {
diff --git a/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
index 5e8179c..4178040 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
@@ -846,7 +846,15 @@
          */
         @NonNull
         public HeaderBuilder setTitle(@NonNull CharSequence title) {
-            mImpl.setTitle(title);
+            return setTitle(title, false /* isLoading */);
+        }
+
+        /**
+         * Sets the title to be shown in this header.
+         */
+        @NonNull
+        public HeaderBuilder setTitle(@NonNull CharSequence title, boolean isLoading) {
+            mImpl.setTitle(title, isLoading);
             return this;
         }
 
@@ -855,7 +863,15 @@
          */
         @NonNull
         public HeaderBuilder setSubtitle(@NonNull CharSequence subtitle) {
-            mImpl.setSubtitle(subtitle);
+            return setSubtitle(subtitle, false /* isLoading */);
+        }
+
+        /**
+         * Sets the subtitle to be shown in this header.
+         */
+        @NonNull
+        public HeaderBuilder setSubtitle(@NonNull CharSequence subtitle, boolean isLoading) {
+            mImpl.setSubtitle(subtitle, isLoading);
             return this;
         }
 
@@ -866,7 +882,18 @@
          */
         @NonNull
         public HeaderBuilder setSummarySubtitle(@NonNull CharSequence summarySubtitle) {
-            mImpl.setSummarySubtitle(summarySubtitle);
+            return setSummarySubtitle(summarySubtitle, false /* isLoading */);
+        }
+
+        /**
+         * Sets the summary subtitle to be shown in this header. If unset, the normal subtitle
+         * will be used. The summary is used when the parent template is presented in a
+         * small format.
+         */
+        @NonNull
+        public HeaderBuilder setSummarySubtitle(@NonNull CharSequence summarySubtitle,
+                boolean isLoading) {
+            mImpl.setSummarySubtitle(summarySubtitle, isLoading);
             return this;
         }
 
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
index 2ea33e3..7f9a57e 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
@@ -302,19 +302,19 @@
         /**
          * Sets the title to be shown in this header.
          */
-        void setTitle(CharSequence title);
+        void setTitle(CharSequence title, boolean isLoading);
 
         /**
          * Sets the subtitle to be shown in this header.
          */
-        void setSubtitle(CharSequence subtitle);
+        void setSubtitle(CharSequence subtitle, boolean isLoading);
 
         /**
          * Sets the summary subtitle to be shown in this header. If unset, the normal subtitle
          * will be used. The summary is used when the parent template is presented in a
          * small format.
          */
-        void setSummarySubtitle(CharSequence summarySubtitle);
+        void setSummarySubtitle(CharSequence summarySubtitle, boolean isLoading);
 
         /**
          * Sets the action to invoke when the header is activated.
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
index 7c283dd..c8750bc 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
@@ -306,21 +306,21 @@
         /**
          */
         @Override
-        public void setTitle(CharSequence title) {
+        public void setTitle(CharSequence title, boolean isLoading) {
 
         }
 
         /**
          */
         @Override
-        public void setSubtitle(CharSequence subtitle) {
+        public void setSubtitle(CharSequence subtitle, boolean isLoading) {
 
         }
 
         /**
          */
         @Override
-        public void setSummarySubtitle(CharSequence summarySubtitle) {
+        public void setSummarySubtitle(CharSequence summarySubtitle, boolean isLoading) {
 
         }
 
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java
index 9c0ca5d..778c54b 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java
@@ -514,9 +514,9 @@
     public static class HeaderBuilderImpl extends TemplateBuilderImpl
             implements ListBuilder.HeaderBuilder {
 
-        private CharSequence mTitle;
-        private CharSequence mSubtitle;
-        private CharSequence mSummarySubtitle;
+        private SliceItem mTitleItem;
+        private SliceItem mSubtitleItem;
+        private SliceItem mSummaryItem;
         private SliceAction mPrimaryAction;
         private CharSequence mContentDescr;
 
@@ -536,14 +536,14 @@
          */
         @Override
         public void apply(Slice.Builder b) {
-            if (mTitle != null) {
-                b.addText(mTitle, null /* subtype */, HINT_TITLE);
+            if (mTitleItem != null) {
+                b.addItem(mTitleItem);
             }
-            if (mSubtitle != null) {
-                b.addText(mSubtitle, null /* subtype */);
+            if (mSubtitleItem != null) {
+                b.addItem(mSubtitleItem);
             }
-            if (mSummarySubtitle != null) {
-                b.addText(mSummarySubtitle, null /* subtype */, HINT_SUMMARY);
+            if (mSummaryItem != null) {
+                b.addItem(mSummaryItem);
             }
             if (mContentDescr != null) {
                 b.addText(mContentDescr, SUBTYPE_CONTENT_DESCRIPTION);
@@ -558,22 +558,32 @@
         /**
          */
         @Override
-        public void setTitle(CharSequence title) {
-            mTitle = title;
+        public void setTitle(CharSequence title, boolean isLoading) {
+            mTitleItem = new SliceItem(title, FORMAT_TEXT, null, new String[] {HINT_TITLE});
+            if (isLoading) {
+                mTitleItem.addHint(HINT_PARTIAL);
+            }
         }
 
         /**
          */
         @Override
-        public void setSubtitle(CharSequence subtitle) {
-            mSubtitle = subtitle;
+        public void setSubtitle(CharSequence subtitle, boolean isLoading) {
+            mSubtitleItem = new SliceItem(subtitle, FORMAT_TEXT, null, new String[0]);
+            if (isLoading) {
+                mSubtitleItem.addHint(HINT_PARTIAL);
+            }
         }
 
         /**
          */
         @Override
-        public void setSummarySubtitle(CharSequence summarySubtitle) {
-            mSummarySubtitle = summarySubtitle;
+        public void setSummarySubtitle(CharSequence summarySubtitle, boolean isLoading) {
+            mSummaryItem = new SliceItem(summarySubtitle, FORMAT_TEXT, null,
+                    new String[] {HINT_SUMMARY});
+            if (isLoading) {
+                mSummaryItem.addHint(HINT_PARTIAL);
+            }
         }
 
         /**