Merge "Fix regression with big picture items" into pi-preview1-androidx-dev
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
index 08a22a4..2b0d291 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
@@ -35,6 +35,16 @@
     private val packagePrefixPattern = Pattern.compile(
         "^(" + config.restrictToPackagePrefixes.map { "($it)" }.joinToString("|") + ").*$")
 
+    /**
+     * Whether to use identity if type in our scope is missing instead of throwing an exception.
+     */
+    val useIdentityIfTypeIsMissing = rewritingSupportLib || isInReversedMode
+
+    /**
+     * Whether to skip verification of dependency version match in pom files.
+     */
+    val ignorePomVersionCheck = rewritingSupportLib || isInReversedMode
+
     /** Counter for [reportNoMappingFoundFailure] calls. */
     var mappingNotFoundFailuresCount = 0
         private set
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
index 4603ae0..3a2ed7e 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
@@ -56,8 +56,12 @@
             return result
         }
 
-        context.reportNoMappingFoundFailure()
-        Log.e(TAG, "No mapping for: " + type)
+        if (context.useIdentityIfTypeIsMissing) {
+            Log.i(TAG, "No mapping for %s - using identity", type)
+        } else {
+            context.reportNoMappingFoundFailure()
+            Log.e(TAG, "No mapping for: " + type)
+        }
         return type
     }
 
@@ -95,8 +99,13 @@
             return path.fileSystem.getPath(result.fullName + ".class")
         }
 
+        if (context.useIdentityIfTypeIsMissing) {
+            Log.i(TAG, "No mapping for: %s", type)
+            return path
+        }
+
         context.reportNoMappingFoundFailure()
-        Log.e(TAG, "No mapping for: " + type)
+        Log.e(TAG, "No mapping for: %s", type)
         return path
     }
 }
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt
index 99b08d5..45b695c 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt
@@ -51,8 +51,7 @@
         session.pomFiles.forEach {
             it.logDocumentDetails()
 
-            // FYI: In reverse mode we don't validate versions
-            if (!context.isInReversedMode && !it.validate(context.config.pomRewriteRules)) {
+            if (!context.ignorePomVersionCheck && !it.validate(context.config.pomRewriteRules)) {
                 Log.e(TAG, "Version mismatch!")
                 validationFailuresCount++
             }
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt
index 28195a3..66a9d6e 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt
@@ -48,14 +48,19 @@
             }
 
             val result = config.typesMap.types[javaType]
-            if (result == null) {
-                context.reportNoProGuardMappingFoundFailure()
-                Log.e(TAG, "No mapping for: " + type)
+            if (result != null) {
+                Log.i(TAG, "  map: %s -> %s", type, result)
+                return result.toDotNotation()
+            }
+
+            if (context.useIdentityIfTypeIsMissing) {
+                Log.i(TAG, "No mapping for: %s - using identity")
                 return typeToReplace
             }
 
-            Log.i(TAG, "  map: %s -> %s", type, result)
-            return result.toDotNotation()
+            context.reportNoProGuardMappingFoundFailure()
+            Log.e(TAG, "No mapping for: %s", type)
+            return typeToReplace
         }
 
         // Type contains wildcards - try custom rules map
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt
index 18c8994..74667d3 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt
@@ -167,8 +167,13 @@
             return result.toDotNotation()
         }
 
+        if (context.useIdentityIfTypeIsMissing) {
+            Log.i(TAG, "No mapping for: %s - using identity", type)
+            return typeName
+        }
+
         context.reportNoMappingFoundFailure()
-        Log.e(TAG, "No mapping for: " + type)
+        Log.e(TAG, "No mapping for: %s", type)
         return typeName
     }
 
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 0901724..770ebc4 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
@@ -36,6 +36,7 @@
 import android.text.style.ForegroundColorSpan;
 import android.util.SparseArray;
 
+import java.util.Arrays;
 import java.util.Calendar;
 
 import androidx.annotation.NonNull;
@@ -511,6 +512,10 @@
             lb.addRow(rb);
         }
 
+        // Add keywords
+        String[] keywords = new String[] {"internet", "wifi", "data", "network"};
+        lb.setKeywords(Arrays.asList(keywords));
+
         // Add see more intent
         if (TEST_CUSTOM_SEE_MORE) {
             lb.addSeeMoreRow(rb -> rb
diff --git a/slices/builders/api/current.txt b/slices/builders/api/current.txt
index cc12c11..03b7c6b 100644
--- a/slices/builders/api/current.txt
+++ b/slices/builders/api/current.txt
@@ -47,6 +47,7 @@
     method public androidx.slice.builders.ListBuilder addSeeMoreRow(java.util.function.Consumer<androidx.slice.builders.ListBuilder.RowBuilder>);
     method public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
     method public androidx.slice.builders.ListBuilder setHeader(java.util.function.Consumer<androidx.slice.builders.ListBuilder.HeaderBuilder>);
+    method public androidx.slice.builders.ListBuilder setKeywords(java.util.List<java.lang.String>);
     field public static final int ICON_IMAGE = 0; // 0x0
     field public static final int LARGE_IMAGE = 2; // 0x2
     field public static final int SMALL_IMAGE = 1; // 0x1
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 4178040..781b2bd 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
@@ -25,6 +25,7 @@
 import android.net.Uri;
 import android.os.Build;
 
+import java.util.List;
 import java.util.function.Consumer;
 
 import androidx.annotation.ColorInt;
@@ -190,6 +191,15 @@
     }
 
     /**
+     * Sets keywords to associate with this slice.
+     */
+    @NonNull
+    public ListBuilder setKeywords(List<String> keywords) {
+        mImpl.setKeywords(keywords);
+        return this;
+    }
+
+    /**
      * If all content in a slice cannot be shown, the row added here may be displayed where the
      * content is cut off. This row should have an affordance to take the user to an activity to
      * see all of the content.
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 7f9a57e..386f215 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
@@ -22,6 +22,8 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 
+import java.util.List;
+
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
@@ -93,6 +95,11 @@
     void setColor(@ColorInt int color);
 
     /**
+     * Sets keywords to associate with this slice.
+     */
+    void setKeywords(List<String> keywords);
+
+    /**
      * Create a builder that implements {@link RowBuilder}.
      */
     TemplateBuilderImpl createRowBuilder();
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 c8750bc..8da1d96 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
@@ -17,11 +17,14 @@
 package androidx.slice.builders.impl;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.slice.core.SliceHints.HINT_KEY_WORDS;
 
 import android.app.PendingIntent;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 
+import java.util.List;
+
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
@@ -100,6 +103,17 @@
     /**
      */
     @Override
+    public void setKeywords(List<String> keywords) {
+        Slice.Builder sb = new Slice.Builder(getBuilder());
+        for (int i = 0; i < keywords.size(); i++) {
+            sb.addText(keywords.get(i), null);
+        }
+        getBuilder().addSubSlice(sb.addHints(HINT_KEY_WORDS).build());
+    }
+
+    /**
+     */
+    @Override
     public TemplateBuilderImpl createRowBuilder() {
         return new RowBuilderImpl(this);
     }
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 778c54b..a9be556 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
@@ -32,6 +32,7 @@
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
 import static androidx.slice.builders.ListBuilder.LARGE_IMAGE;
+import static androidx.slice.core.SliceHints.HINT_KEY_WORDS;
 import static androidx.slice.core.SliceHints.SUBTYPE_MAX;
 import static androidx.slice.core.SliceHints.SUBTYPE_RANGE;
 import static androidx.slice.core.SliceHints.SUBTYPE_VALUE;
@@ -242,6 +243,17 @@
     /**
      */
     @Override
+    public void setKeywords(@NonNull List<String> keywords) {
+        Slice.Builder sb = new Slice.Builder(getBuilder());
+        for (int i = 0; i < keywords.size(); i++) {
+            sb.addText(keywords.get(i), null);
+        }
+        getBuilder().addSubSlice(sb.addHints(HINT_KEY_WORDS).build());
+    }
+
+    /**
+     */
+    @Override
     public TemplateBuilderImpl createRowBuilder() {
         return new RowBuilderImpl(this);
     }
diff --git a/slices/core/src/main/java/androidx/slice/core/SliceHints.java b/slices/core/src/main/java/androidx/slice/core/SliceHints.java
index f764d99..a1ffb42 100644
--- a/slices/core/src/main/java/androidx/slice/core/SliceHints.java
+++ b/slices/core/src/main/java/androidx/slice/core/SliceHints.java
@@ -54,4 +54,11 @@
      * the activity.
      */
     public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+
+    /**
+     * A hint to indicate that the contents of this subslice represent a list of keywords
+     * related to the parent slice.
+     */
+    public static final String HINT_KEY_WORDS = "key_words";
+
 }
diff --git a/slices/view/api/current.txt b/slices/view/api/current.txt
index 43e17d3..f9c305c 100644
--- a/slices/view/api/current.txt
+++ b/slices/view/api/current.txt
@@ -19,6 +19,7 @@
   public class SliceUtils {
     method public static int getLoadingState(androidx.slice.Slice);
     method public static java.util.List<androidx.slice.SliceItem> getSliceActions(androidx.slice.Slice);
+    method public static java.util.List<java.lang.String> getSliceKeywords(androidx.slice.Slice);
     method public static androidx.slice.Slice parseSlice(java.io.InputStream, java.lang.String) throws java.io.IOException;
     method public static void serializeSlice(androidx.slice.Slice, android.content.Context, java.io.OutputStream, java.lang.String, androidx.slice.SliceUtils.SerializeOptions) throws java.io.IOException;
     field public static final int LOADING_ALL = 0; // 0x0
diff --git a/slices/view/src/androidTest/java/androidx/slice/SliceXmlTest.java b/slices/view/src/androidTest/java/androidx/slice/SliceXmlTest.java
index 926b72c..2cae298 100644
--- a/slices/view/src/androidTest/java/androidx/slice/SliceXmlTest.java
+++ b/slices/view/src/androidTest/java/androidx/slice/SliceXmlTest.java
@@ -17,6 +17,7 @@
 package androidx.slice;
 
 
+import static android.app.slice.Slice.HINT_TITLE;
 import static android.app.slice.SliceItem.FORMAT_ACTION;
 import static android.app.slice.SliceItem.FORMAT_SLICE;
 import static android.app.slice.SliceItem.FORMAT_TEXT;
@@ -24,6 +25,11 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNull;
+
+import static androidx.slice.core.SliceHints.HINT_KEY_WORDS;
+
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -39,6 +45,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -137,6 +144,41 @@
         assertEquivalent(before, after);
     }
 
+    @Test
+    public void testKeywords() {
+        Uri uri = Uri.parse("content://pkg/slice");
+        Slice keywordSlice = new Slice.Builder(uri)
+                .addHints(HINT_KEY_WORDS)
+                .addText("keyword1", null)
+                .addText("keyword2", null)
+                .addText("keyword3", null).build();
+        Slice slice = new Slice.Builder(uri)
+                .addText("Some text", null, HINT_TITLE)
+                .addText("Some other text", null)
+                .addSubSlice(keywordSlice)
+                .build();
+
+        List<String> sliceKeywords = SliceUtils.getSliceKeywords(slice);
+        String[] expectedList = new String[] {"keyword1", "keyword2", "keyword3"};
+        assertArrayEquals(expectedList, sliceKeywords.toArray());
+
+        // Make sure it doesn't find keywords that aren't there
+        Slice slice2 = new Slice.Builder(uri)
+                .addText("Some text", null, HINT_TITLE)
+                .addText("Some other text", null).build();
+        List<String> slice2Keywords = SliceUtils.getSliceKeywords(slice2);
+        assertNull(slice2Keywords);
+
+        // Make sure empty list if specified to have no keywords
+        Slice noKeywordSlice = new Slice.Builder(uri).addHints(HINT_KEY_WORDS).build();
+        Slice slice3 = new Slice.Builder(uri)
+                .addText("Some text", null, HINT_TITLE)
+                .addSubSlice(noKeywordSlice)
+                .build();
+        List<String> slice3Keywords = SliceUtils.getSliceKeywords(slice3);
+        assertTrue(slice3Keywords.isEmpty());
+    }
+
     private void assertEquivalent(Slice desired, Slice actual) {
         assertEquals(desired.getUri(), actual.getUri());
         assertEquals(desired.getHints(), actual.getHints());
diff --git a/slices/view/src/androidTest/java/androidx/slice/render/SliceCreator.java b/slices/view/src/androidTest/java/androidx/slice/render/SliceCreator.java
index 8e07b27..26c7704 100644
--- a/slices/view/src/androidTest/java/androidx/slice/render/SliceCreator.java
+++ b/slices/view/src/androidTest/java/androidx/slice/render/SliceCreator.java
@@ -34,6 +34,8 @@
 import android.text.format.DateUtils;
 import android.text.style.ForegroundColorSpan;
 
+import java.util.Arrays;
+
 import androidx.slice.Slice;
 import androidx.slice.builders.GridBuilder;
 import androidx.slice.builders.ListBuilder;
@@ -383,6 +385,10 @@
                         "Toggle wifi", finalWifiEnabled))
                 .setPrimaryAction(primaryAction));
 
+        // Add keywords
+        String[] keywords = new String[] {"internet", "wifi", "data", "network"};
+        lb.setKeywords(Arrays.asList(keywords));
+
         // Add fake wifi networks
         int[] wifiIcons = new int[] {R.drawable.ic_wifi_full, R.drawable.ic_wifi_low,
                 R.drawable.ic_wifi_fair};
diff --git a/slices/view/src/main/java/androidx/slice/SliceUtils.java b/slices/view/src/main/java/androidx/slice/SliceUtils.java
index 51fcd0c..32015f5 100644
--- a/slices/view/src/main/java/androidx/slice/SliceUtils.java
+++ b/slices/view/src/main/java/androidx/slice/SliceUtils.java
@@ -23,18 +23,23 @@
 import static android.app.slice.SliceItem.FORMAT_IMAGE;
 import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
 import static android.app.slice.SliceItem.FORMAT_SLICE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
+import static androidx.slice.core.SliceHints.HINT_KEY_WORDS;
 
 import android.content.Context;
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
+import android.text.TextUtils;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.List;
 
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
 import androidx.slice.core.SliceQuery;
 
 /**
@@ -219,4 +224,27 @@
                 ? SliceQuery.findAll(actionGroup, FORMAT_SLICE, hints, null)
                 : null;
     }
+
+    /**
+     * @return the list of keywords associated with the provided slice, null if no keywords were
+     * specified or an empty list if the slice was specified to have no keywords.
+     */
+    @Nullable
+    public static List<String> getSliceKeywords(@NonNull Slice slice) {
+        SliceItem keywordGroup = SliceQuery.find(slice, FORMAT_SLICE, HINT_KEY_WORDS, null);
+        if (keywordGroup != null) {
+            List<SliceItem> itemList = SliceQuery.findAll(keywordGroup, FORMAT_TEXT);
+            if (itemList != null) {
+                ArrayList<String> stringList = new ArrayList<>();
+                for (int i = 0; i < itemList.size(); i++) {
+                    String keyword = (String) itemList.get(i).getText();
+                    if (!TextUtils.isEmpty(keyword)) {
+                        stringList.add(keyword);
+                    }
+                }
+                return stringList;
+            }
+        }
+        return null;
+    }
 }
diff --git a/slices/view/src/main/java/androidx/slice/widget/GridContent.java b/slices/view/src/main/java/androidx/slice/widget/GridContent.java
index fd9b507..64ead75 100644
--- a/slices/view/src/main/java/androidx/slice/widget/GridContent.java
+++ b/slices/view/src/main/java/androidx/slice/widget/GridContent.java
@@ -33,6 +33,7 @@
 import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
 import static androidx.slice.builders.ListBuilder.LARGE_IMAGE;
 import static androidx.slice.builders.ListBuilder.SMALL_IMAGE;
+import static androidx.slice.core.SliceHints.HINT_KEY_WORDS;
 
 import android.app.slice.Slice;
 import android.content.Context;
@@ -196,8 +197,8 @@
             SliceItem item = items.get(i);
             if (SUBTYPE_CONTENT_DESCRIPTION.equals(item.getSubType())) {
                 mContentDescr = item;
-            } else if (item.hasHint(HINT_LIST_ITEM) && !item.hasHint(HINT_SHORTCUT)
-                    && !item.hasHint(HINT_SEE_MORE)) {
+            } else if (item.hasHint(HINT_LIST_ITEM) && !item.hasAnyHints(HINT_SHORTCUT,
+                    HINT_SEE_MORE, HINT_KEY_WORDS)) {
                 filteredItems.add(item);
             }
         }
@@ -332,7 +333,9 @@
          */
         private boolean isValidCellContent(SliceItem cellItem) {
             final String format = cellItem.getFormat();
-            return !SUBTYPE_CONTENT_DESCRIPTION.equals(cellItem.getSubType())
+            boolean isSpecial = SUBTYPE_CONTENT_DESCRIPTION.equals(cellItem.getSubType())
+                    || cellItem.hasHint(HINT_KEY_WORDS);
+            return !isSpecial
                     && (FORMAT_TEXT.equals(format)
                     || FORMAT_TIMESTAMP.equals(format)
                     || FORMAT_IMAGE.equals(format));
diff --git a/slices/view/src/main/java/androidx/slice/widget/ListContent.java b/slices/view/src/main/java/androidx/slice/widget/ListContent.java
index 580d9d7..5259ce7 100644
--- a/slices/view/src/main/java/androidx/slice/widget/ListContent.java
+++ b/slices/view/src/main/java/androidx/slice/widget/ListContent.java
@@ -27,14 +27,16 @@
 import static android.app.slice.SliceItem.FORMAT_SLICE;
 import static android.app.slice.SliceItem.FORMAT_TEXT;
 
+import static androidx.slice.core.SliceHints.HINT_KEY_WORDS;
+
 import android.content.Context;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
 import androidx.slice.Slice;
 import androidx.slice.SliceItem;
 import androidx.slice.SliceUtils;
@@ -87,7 +89,7 @@
         for (int i = 0; i < children.size(); i++) {
             final SliceItem child = children.get(i);
             final String format = child.getFormat();
-            if (!child.hasAnyHints(HINT_ACTIONS, HINT_SEE_MORE)
+            if (!child.hasAnyHints(HINT_ACTIONS, HINT_SEE_MORE, HINT_KEY_WORDS)
                     && (FORMAT_ACTION.equals(format) || FORMAT_SLICE.equals(format))) {
                 if (mHeaderItem == null && !child.hasHint(HINT_LIST_ITEM)) {
                     mHeaderItem = child;
@@ -209,7 +211,8 @@
     @Nullable
     private static SliceItem findHeaderItem(@NonNull Slice slice) {
         // See if header is specified
-        String[] nonHints = new String[] {HINT_LIST_ITEM, HINT_SHORTCUT, HINT_ACTIONS};
+        String[] nonHints = new String[] {HINT_LIST_ITEM, HINT_SHORTCUT, HINT_ACTIONS,
+                HINT_KEY_WORDS};
         SliceItem header = SliceQuery.find(slice, FORMAT_SLICE, null, nonHints);
         if (header != null && isValidHeader(header)) {
             return header;
@@ -233,8 +236,8 @@
     }
 
     private static boolean isValidHeader(SliceItem sliceItem) {
-        if (FORMAT_SLICE.equals(sliceItem.getFormat()) && !sliceItem.hasHint(HINT_LIST_ITEM)
-                && !sliceItem.hasHint(HINT_ACTIONS)) {
+        if (FORMAT_SLICE.equals(sliceItem.getFormat()) && !sliceItem.hasAnyHints(HINT_LIST_ITEM,
+                HINT_ACTIONS, HINT_KEY_WORDS)) {
              // Minimum valid header is a slice with text
             SliceItem item = SliceQuery.find(sliceItem, FORMAT_TEXT, (String) null, null);
             return item != null;
diff --git a/slices/view/src/main/java/androidx/slice/widget/RowContent.java b/slices/view/src/main/java/androidx/slice/widget/RowContent.java
index d7f7ab6..b6ca48a 100644
--- a/slices/view/src/main/java/androidx/slice/widget/RowContent.java
+++ b/slices/view/src/main/java/androidx/slice/widget/RowContent.java
@@ -30,6 +30,7 @@
 import static android.app.slice.SliceItem.FORMAT_TEXT;
 import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
 
+import static androidx.slice.core.SliceHints.HINT_KEY_WORDS;
 import static androidx.slice.core.SliceHints.SUBTYPE_RANGE;
 
 import android.content.Context;
@@ -88,7 +89,7 @@
         // Find primary action first (otherwise filtered out of valid row items)
         String[] hints = new String[] {HINT_SHORTCUT, HINT_TITLE};
         mPrimaryAction = SliceQuery.find(rowSlice, FORMAT_SLICE, hints,
-                new String[] { HINT_ACTIONS } /* nonHints */);
+                new String[] { HINT_ACTIONS, HINT_KEY_WORDS } /* nonHints */);
 
         mContentDescr = SliceQuery.findSubtype(rowSlice, FORMAT_TEXT, SUBTYPE_CONTENT_DESCRIPTION);
 
@@ -331,7 +332,11 @@
      * @return whether this item is valid content to display in a row.
      */
     private static boolean isValidRowContent(SliceItem slice, SliceItem item) {
-        if (FORMAT_SLICE.equals(item.getFormat()) && !item.hasHint(HINT_SHORTCUT)) {
+        if (item.hasHint(HINT_KEY_WORDS)) {
+            return false;
+        }
+        if (FORMAT_SLICE.equals(item.getFormat())
+                && !item.hasAnyHints(HINT_SHORTCUT, HINT_KEY_WORDS)) {
             // Unpack contents of slice
             item = item.getSlice().getItems().get(0);
         }
diff --git a/v7/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollerTest.java b/v7/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollerTest.java
new file mode 100644
index 0000000..c179f58
--- /dev/null
+++ b/v7/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollerTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+package androidx.recyclerview.widget;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RecyclerViewSmoothScrollerTest {
+
+    @Test
+    public void stop_whileRunning_isRunningIsFalseInOnStop() {
+        RecyclerView recyclerView = new RecyclerView(InstrumentationRegistry.getContext());
+        RecyclerView.LayoutManager layoutManager = mock(RecyclerView.LayoutManager.class);
+        recyclerView.setLayoutManager(layoutManager);
+        MockSmoothScroller mockSmoothScroller = spy(new MockSmoothScroller());
+        mockSmoothScroller.setTargetPosition(0);
+        mockSmoothScroller.start(recyclerView, layoutManager);
+
+        mockSmoothScroller.stop();
+
+        verify(mockSmoothScroller).onStop();
+        assertThat(mockSmoothScroller.mWasRunningInOnStop, is(false));
+    }
+
+    @Test
+    public void stop_whileNotRunning_doesNotCallOnStop() {
+        RecyclerView.SmoothScroller mockSmoothScroller = spy(new MockSmoothScroller());
+        mockSmoothScroller.stop();
+        verify(mockSmoothScroller, never()).onStop();
+    }
+
+    public static class MockSmoothScroller extends RecyclerView.SmoothScroller {
+
+        boolean mWasRunningInOnStop;
+
+        @Override
+        protected void onStart() {
+
+        }
+
+        @Override
+        protected void onStop() {
+            mWasRunningInOnStop = isRunning();
+        }
+
+        @Override
+        protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state,
+                Action action) {
+
+        }
+
+        @Override
+        protected void onTargetFound(View targetView, RecyclerView.State state,
+                Action action) {
+
+        }
+    }
+
+}
diff --git a/v7/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/v7/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index 05b5c45..8fdb0f5 100644
--- a/v7/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/v7/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -37,30 +37,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import androidx.annotation.CallSuper;
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.Px;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.os.TraceCompat;
-import androidx.core.util.Preconditions;
-import androidx.customview.view.AbsSavedState;
-import androidx.core.view.InputDeviceCompat;
-import androidx.core.view.MotionEventCompat;
-import androidx.core.view.NestedScrollingChild2;
-import androidx.core.view.NestedScrollingChildHelper;
-import androidx.core.view.ScrollingView;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.ViewConfigurationCompat;
-import androidx.core.view.accessibility.AccessibilityEventCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.core.widget.EdgeEffectCompat;
-import androidx.recyclerview.R;
-import androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
-import androidx.viewpager.widget.ViewPager;
-
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -89,6 +65,30 @@
 import java.util.Collections;
 import java.util.List;
 
+import androidx.annotation.CallSuper;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.os.TraceCompat;
+import androidx.core.util.Preconditions;
+import androidx.core.view.InputDeviceCompat;
+import androidx.core.view.MotionEventCompat;
+import androidx.core.view.NestedScrollingChild2;
+import androidx.core.view.NestedScrollingChildHelper;
+import androidx.core.view.ScrollingView;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.ViewConfigurationCompat;
+import androidx.core.view.accessibility.AccessibilityEventCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.core.widget.EdgeEffectCompat;
+import androidx.customview.view.AbsSavedState;
+import androidx.recyclerview.R;
+import androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
+import androidx.viewpager.widget.ViewPager;
+
 /**
  * A flexible view for providing a limited window into a large data set.
  *
@@ -11472,12 +11472,12 @@
             if (!mRunning) {
                 return;
             }
+            mRunning = false;
             onStop();
             mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;
             mTargetView = null;
             mTargetPosition = RecyclerView.NO_POSITION;
             mPendingInitialRun = false;
-            mRunning = false;
             // trigger a cleanup
             mLayoutManager.onSmoothScrollerStopped(this);
             // clear references to avoid any potential leak by a custom smooth scroller