[automerger] Update ListItem API from Builder to setters. am: 1724255122

Change-Id: Ia9825f2f967625590922d9f1922fa019b2b2377e
diff --git a/.gitignore b/.gitignore
index 7e44717..ac3565c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+/unbundled-build
 .classpath
 .gradle
 **/.idea/**
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 7b2174e..7dc4050 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -46,6 +46,8 @@
 
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/support.aidl)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-customtabs_intermediates/src/)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-annotations_intermediates)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/OWNERS b/OWNERS
index 0de3634..3126105 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,6 +1,7 @@
 adamp@google.com
 alanv@google.com
 aurimas@google.com
+chet@google.com
 ccraik@google.com
 clarabayarri@google.com
 ilake@google.com
diff --git a/README.md b/README.md
index d34ea7e..f6cdb65 100644
--- a/README.md
+++ b/README.md
@@ -29,12 +29,19 @@
 
 If you see any warnings (red underlines) run `Build > Clean Project`.
 
-## Optional - Full Build
+## Builds
+### Full Build (Optional)
 You can do most of your work from Android Studio, however you can also build the full support library from command line:
 
     cd path/to/checkout/frameworks/support/
     ./gradlew createArchive
 
+### Building Support Library as part of your App build
+If you intend to repeatedly make changes to Support Library and to wish to see
+the results in your app, and you don't want to have to repeatedly build them as
+separate Gradle projects, you can
+[configure your app build to build Support Library too](adding-support-library-as-included-build.md)
+
 ## Running Tests
 
 ### Single Test Class or Method
diff --git a/adding-support-library-as-included-build.md b/adding-support-library-as-included-build.md
new file mode 100644
index 0000000..136929f
--- /dev/null
+++ b/adding-support-library-as-included-build.md
@@ -0,0 +1,32 @@
+# Adding the Support Library Build Within Another Build
+
+Would you like to make a change in Support Library and have it be propagated to
+your downstream Gradle build (generally an app) without having to separately
+build Support Library and then build your application?
+
+## To build Support Library as part of your existing Gradle build
+*   To add the Support Library build
+    *   Add `apply(from: '<support-lib-repo-root>/frameworks/support/include-support-library.gradle')`
+        to your settings.gradle
+        *   See [include-support-library.gradle](include-support-library.gradle)
+            for more information
+*   If your project is an Android app, also update some dependencies:
+    *   Open your local.properties file and update the value of `sdk.dir` .
+        *   It should point to `<support-lib-repo-root>/prebuilts/fullsdk-<platform>` .
+        *   For example, `~/support-library/prebuilts/fullsdk-linux` .
+    *   In your build.gradle, update any versions that refer to previous versions of
+        Support Library.
+        *   To determine the correct version, find the SDK with the highest
+            number among SDKs in the Support Library repo.
+
+                echo <support-lib-repo-root>/prebuilts/fullsdk-linux/platforms/android* | xargs -n 1 echo | sed 's/.*android-//' | tail -n 1
+
+            This should output, for example, "28"
+
+        *   Update dependency versions
+            *   For example, you may want to replace
+                `com.android.support:app-compat-v7:26.0.2` with
+                `com.android.support:app-compat-v7:28.0.2`
+        *   Update configuration given to the Android Gradle plugin
+            *   Check `compileSdkVersion` and make sure its version is correct
+
diff --git a/app-toolkit/settings.gradle b/app-toolkit/settings.gradle
index 43237a4..18176de 100644
--- a/app-toolkit/settings.gradle
+++ b/app-toolkit/settings.gradle
@@ -113,10 +113,5 @@
 // External
 //
 /////////////////////////////
-if (inAppToolkitProject) {
-    File externalRoot = new File(supportRoot, '../../external')
 
-    includeBuild new File(externalRoot, 'doclava')
-
-    includeBuild new File(externalRoot, 'jdiff')
-}
+apply(from: new File(supportRoot, 'include-composite-deps.gradle'))
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 4d663af..68c77b7 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -1,9 +1,13 @@
 buildscript {
     def supportRootFolder = project.projectDir.getParentFile()
+    apply from: "unbundled_check.gradle"
     repositories {
         maven {
             url "${supportRootFolder}/../../prebuilts/tools/common/m2/repository"
         }
+        if (isUnbundledBuild(supportRootFolder)) {
+            jcenter()
+        }
     }
 
     apply from: "build_dependencies.gradle"
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index fe664e0..baf1ba0 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -17,12 +17,7 @@
 def libs = [:]
 
 libs.exclude_annotations_transitive = {
-    exclude module: 'support-annotations'
-    transitive = true
-}
-
-libs.exclude_annotations_transitive = {
-    exclude module: 'support-annotations'
+    exclude group: 'com.android.support'
     transitive = true
 }
 
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index 30841b6..b89bd92 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -35,6 +35,8 @@
 
 apply from: "${supportRoot}/buildSrc/dependencies.gradle"
 apply from: "${supportRoot}/buildSrc/build_dependencies.gradle"
+apply from: "${supportRoot}/buildSrc/unbundled_check.gradle"
+
 
 def enableDoclavaAndJDiff(p, dacOptions) {
     p.configurations {
@@ -55,11 +57,53 @@
 }
 
 def getFullSdkPath() {
-    final String osName = System.getProperty("os.name").toLowerCase();
-    final boolean isMacOsX =
-            osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx");
-    final String platform = isMacOsX ? 'darwin' : 'linux'
-    return "${repos.prebuiltsRoot}/fullsdk-${platform}"
+    if (isUnbundledBuild(ext.supportRootFolder)) {
+        Properties properties = new Properties()
+        File propertiesFile = new File('local.properties')
+        if (propertiesFile.exists()) {
+            propertiesFile.withInputStream {
+                properties.load(it)
+            }
+        }
+        File location = findSdkLocation(properties, supportRootFolder)
+        return location.getAbsolutePath()
+    } else {
+        final String osName = System.getProperty("os.name").toLowerCase();
+        final boolean isMacOsX =
+                osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx");
+        final String platform = isMacOsX ? 'darwin' : 'linux'
+        return "${repos.prebuiltsRoot}/fullsdk-${platform}"
+    }
+}
+
+/**
+ * Adapted from com.android.build.gradle.internal.SdkHandler
+ */
+public static File findSdkLocation(Properties properties, File rootDir) {
+    String sdkDirProp = properties.getProperty("sdk.dir");
+    if (sdkDirProp != null) {
+        File sdk = new File(sdkDirProp);
+        if (!sdk.isAbsolute()) {
+            sdk = new File(rootDir, sdkDirProp);
+        }
+        return sdk
+    }
+
+    sdkDirProp = properties.getProperty("android.dir");
+    if (sdkDirProp != null) {
+        return new File(rootDir, sdkDirProp);
+    }
+
+    String envVar = System.getenv("ANDROID_HOME");
+    if (envVar != null) {
+        return new File(envVar);
+    }
+
+    String property = System.getProperty("android.home");
+    if (property != null) {
+        return new File(property);
+    }
+    return null;
 }
 
 def setSdkInLocalPropertiesFile() {
diff --git a/buildSrc/repos.gradle b/buildSrc/repos.gradle
index ad5a621..9785a74 100644
--- a/buildSrc/repos.gradle
+++ b/buildSrc/repos.gradle
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-
 String getFullSdkPath(String prebuiltsRoot) {
     final String osName = System.getProperty("os.name").toLowerCase()
     final boolean isMacOsX =
@@ -30,7 +28,9 @@
             " including this script")
 }
 
-def checkoutRoot = "${supportRoot}/../.."
+apply from: "${supportRoot}/buildSrc/unbundled_check.gradle"
+
+def checkoutRoot = "${ext.supportRootFolder}/../.."
 ext.repos = new Properties()
 ext.repos.prebuiltsRoot = "${checkoutRoot}/prebuilts"
 ext.repos.prebuiltsRootUri = "file://${repos.prebuiltsRoot}"
@@ -49,10 +49,13 @@
             url repo
         }
     }
-    if (System.getenv("ALLOW_PUBLIC_REPOS") != null) {
+    if (System.getenv("ALLOW_PUBLIC_REPOS") != null || (isUnbundledBuild(ext.supportRootFolder))) {
         handler.mavenCentral()
         handler.jcenter()
         handler.google()
+        handler.maven {
+            url "https://plugins.gradle.org/m2/"
+        }
     }
     def androidPluginRepoOverride = System.getenv("GRADLE_PLUGIN_REPO")
     if (androidPluginRepoOverride != null) {
diff --git a/buildSrc/src/main/java/android/support/LibraryGroups.java b/buildSrc/src/main/java/android/support/LibraryGroups.java
index feaefbc..19c0a92 100644
--- a/buildSrc/src/main/java/android/support/LibraryGroups.java
+++ b/buildSrc/src/main/java/android/support/LibraryGroups.java
@@ -27,4 +27,5 @@
     public static final String ARCH_CORE = "android.arch.core";
     public static final String PAGING = "android.arch.paging";
     public static final String NAVIGATION = "android.arch.navigation";
+    public static final String SLICES = "androidx.app.slice";
 }
diff --git a/buildSrc/src/main/java/android/support/LibraryVersions.java b/buildSrc/src/main/java/android/support/LibraryVersions.java
index 0da12c2..813d9a8 100644
--- a/buildSrc/src/main/java/android/support/LibraryVersions.java
+++ b/buildSrc/src/main/java/android/support/LibraryVersions.java
@@ -23,7 +23,7 @@
     /**
      * Version code of the support library components.
      */
-    public static final Version SUPPORT_LIBRARY = new Version("27.1.0-SNAPSHOT");
+    public static final Version SUPPORT_LIBRARY = new Version("28.0.0-SNAPSHOT");
 
     /**
      * Version code for Room
diff --git a/buildSrc/src/main/kotlin/android/support/SupportConfig.kt b/buildSrc/src/main/kotlin/android/support/SupportConfig.kt
index 94573c6..42b298c 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportConfig.kt
+++ b/buildSrc/src/main/kotlin/android/support/SupportConfig.kt
@@ -23,8 +23,8 @@
 object SupportConfig {
     const val DEFAULT_MIN_SDK_VERSION = 14
     const val INSTRUMENTATION_RUNNER = "android.support.test.runner.AndroidJUnitRunner"
-    const val BUILD_TOOLS_VERSION = "27.0.1"
-    const val CURRENT_SDK_VERSION = 27
+    const val BUILD_TOOLS_VERSION = "27.0.3"
+    const val CURRENT_SDK_VERSION = 28
 
     fun getKeystore(project: Project): File {
         val supportRoot = (project.rootProject.property("ext") as ExtraPropertiesExtension)
diff --git a/buildSrc/unbundled_check.gradle b/buildSrc/unbundled_check.gradle
new file mode 100644
index 0000000..b80daf7
--- /dev/null
+++ b/buildSrc/unbundled_check.gradle
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public boolean isUnbundledBuild(File rootDirectory) {
+    return (new File(rootDirectory, "unbundled-build")).exists();
+}
+
+rootProject.ext['isUnbundledBuild'] = this.&isUnbundledBuild
\ No newline at end of file
diff --git a/car/Android.mk b/car/Android.mk
index 18f08e4..a8bb8ce 100644
--- a/car/Android.mk
+++ b/car/Android.mk
@@ -18,7 +18,12 @@
 # Applications that use this library must specify
 #
 #   LOCAL_STATIC_ANDROID_LIBRARIES := \
-#       android-support-car
+#       android-support-car\
+#       android-support-design \
+#       android-support-v4 \
+#       android-support-v7-appcompat \
+#       android-support-v7-cardview \
+#       android-support-v7-recyclerview
 #
 # in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
@@ -30,6 +35,7 @@
 LOCAL_JAVA_LIBRARIES := \
         android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
+        android-support-design \
         android-support-v4 \
         android-support-v7-appcompat \
         android-support-v7-cardview \
diff --git a/car/build.gradle b/car/build.gradle
index 6aa1865..0e42b6c 100644
--- a/car/build.gradle
+++ b/car/build.gradle
@@ -9,6 +9,7 @@
 dependencies {
     api project(':appcompat-v7')
     api project(':cardview-v7')
+    api project(':design')
     api project(':support-annotations')
     api project(':support-v4')
     api project(':recyclerview-v7')
diff --git a/car/res/layout/car_drawer_activity.xml b/car/res/layout/car_drawer_activity.xml
index 751ef0d..9bc7279 100644
--- a/car/res/layout/car_drawer_activity.xml
+++ b/car/res/layout/car_drawer_activity.xml
@@ -13,28 +13,44 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-<FrameLayout
-      xmlns:android="http://schemas.android.com/apk/res/android"
-      android:layout_width="match_parent"
-      android:layout_height="match_parent">
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
     <android.support.v4.widget.DrawerLayout
         android:id="@+id/drawer_layout"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
-      <!-- The main content view. Fragments will be added here. -->
-      <FrameLayout
-          android:id="@+id/content_frame"
-          android:layout_width="match_parent"
-          android:layout_height="match_parent" />
+        <!-- The main content view. Fragments will be added here. -->
+        <FrameLayout
+            android:id="@+id/content_frame"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layout_behavior="@string/appbar_scrolling_view_behavior" />
 
-      <include
-          android:layout_width="match_parent"
-          android:layout_height="match_parent"
-          android:layout_gravity="start"
-          layout="@layout/car_drawer" />
+        <include
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="start"
+            layout="@layout/car_drawer" />
     </android.support.v4.widget.DrawerLayout>
 
-    <include layout="@layout/car_toolbar" />
-</FrameLayout>
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/appbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fitsSystemWindows="true">
+        <!-- The min height of the Toolbar needs to be set to ensure that the icons in it
+             are vertically centered. -->
+        <androidx.car.widget.ClickThroughToolbar
+            android:id="@+id/car_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/car_app_bar_height"
+            android:layout_gravity="center_vertical"
+            android:minHeight="@dimen/car_app_bar_height"
+            style="@style/CarToolbarTheme" />
+    </android.support.design.widget.AppBarLayout>
+</android.support.design.widget.CoordinatorLayout>
diff --git a/car/res/values-night/colors.xml b/car/res/values-night/colors.xml
index 7e29a5b..4ebfaed 100644
--- a/car/res/values-night/colors.xml
+++ b/car/res/values-night/colors.xml
@@ -37,4 +37,6 @@
     <color name="car_list_divider">@color/car_list_divider_dark</color>
     <color name="car_scrollbar_thumb">@color/car_scrollbar_thumb_light</color>
     <color name="car_scrollbar_thumb_inverse">@color/car_scrollbar_thumb_dark</color>
+
+    <color name="car_accent">@color/car_accent_light</color>
 </resources>
diff --git a/car/res/values/attrs.xml b/car/res/values/attrs.xml
index 0b9f0ec..8b32b24 100644
--- a/car/res/values/attrs.xml
+++ b/car/res/values/attrs.xml
@@ -42,10 +42,18 @@
             <!-- Include a gutter on both sides of the list view items. -->
             <enum name="both" value="3" />
         </attr>
+        <!-- The size of the gutter that is either at the start, end or both sides of the
+             items in the PagedListView. There is a default value that changes per screen size if
+             a gutter size is not explicitly set.-->
+        <attr name="gutterSize" format="dimension" />
         <!-- Whether to display the scrollbar or not. Defaults to true. -->
         <attr name="scrollBarEnabled" format="boolean" />
         <!-- The top margin before the scroll bar is drawn. -->
         <attr name="scrollBarTopMargin" format="dimension" />
+        <!-- The width of the container that will hold the scrollbar. The scrollbar is centered
+             within this value. If this value is not explicitly set, the scrollbar centers itself
+             within the car_margin value. -->
+        <attr name="scrollBarContainerWidth" format="dimension" />
         <!-- Whether or not to show a diving line between each item of the list. -->
         <attr name="showPagedListViewDivider" format="boolean" />
         <!-- An optional id that specifies a child View whose starting edge will be used to
@@ -66,6 +74,9 @@
         <attr name="upButtonIcon" format="reference" />
         <!-- The icon to be used for the down button of the scroll bar.  -->
         <attr name="downButtonIcon" format="reference" />
+        <!-- The amount of space before the first item in the list view. This space is
+             scrollable with the contents of the list. -->
+        <attr name="listContentTopOffset" format="reference" />
     </declare-styleable>
 
     <!-- The attributes for customizing the appearance of the hamburger and back arrow in the
@@ -88,4 +99,11 @@
         <!-- The size of the menu bars (i.e. the "hamburger" icon). -->
         <attr name="carMenuBarThickness" format="dimension"/>
     </declare-styleable>
+
+    <!-- Attributes for the ClickThroughToolbar. -->
+    <declare-styleable name="ClickThroughToolbar">
+        <!-- Whether or not clicks on this toolbar will pass through to an underlying view. This
+             value is false by default. -->
+        <attr name="clickThrough" format="boolean"/>
+    </declare-styleable>
 </resources>
diff --git a/car/res/values/colors.xml b/car/res/values/colors.xml
index 9cac744..a32feff 100644
--- a/car/res/values/colors.xml
+++ b/car/res/values/colors.xml
@@ -177,7 +177,7 @@
     <color name="car_highlight_dark">@color/car_teal_200</color>
     <color name="car_highlight">@color/car_highlight_light</color>
 
-    <color name="car_accent_light">@color/car_teal_700</color>
-    <color name="car_accent_dark">@color/car_teal_200</color>
-    <color name="car_accent">@color/car_accent_light</color>
+    <color name="car_accent_dark">@color/car_teal_700</color>
+    <color name="car_accent_light">@color/car_teal_200</color>
+    <color name="car_accent">@color/car_accent_dark</color>
 </resources>
diff --git a/car/res/values/dimens.xml b/car/res/values/dimens.xml
index fa1eadf..212cc9a 100644
--- a/car/res/values/dimens.xml
+++ b/car/res/values/dimens.xml
@@ -79,6 +79,7 @@
 
     <!-- Application Bar -->
     <dimen name="car_app_bar_height">80dp</dimen>
+    <dimen name="car_app_bar_default_elevation">8dp</dimen>
 
     <!-- Action Bars -->
     <dimen name="car_action_bar_height">128dp</dimen>
diff --git a/car/res/values/styles.xml b/car/res/values/styles.xml
index a0a1508..47ced9b 100644
--- a/car/res/values/styles.xml
+++ b/car/res/values/styles.xml
@@ -137,6 +137,7 @@
     <style name="Widget.Car.Button.Borderless.Colored"
            parent="Widget.AppCompat.Button.Borderless.Colored">
         <item name="android:layout_height">@dimen/car_button_height</item>
+        <item name="android:minWidth">@dimen/car_button_min_width</item>
         <item name="android:paddingStart">@dimen/car_borderless_button_horizontal_padding</item>
         <item name="android:paddingEnd">@dimen/car_borderless_button_horizontal_padding</item>
         <item name="android:textSize">@dimen/car_action1_size</item>
diff --git a/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java b/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java
index 9769142..3929cca 100644
--- a/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java
+++ b/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java
@@ -16,20 +16,24 @@
 
 package androidx.car.drawer;
 
+import android.animation.ValueAnimator;
 import android.content.res.Configuration;
+import android.graphics.Color;
 import android.os.Bundle;
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
+import android.support.design.widget.AppBarLayout;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v7.app.ActionBarDrawerToggle;
 import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
+import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.car.R;
+import androidx.car.widget.ClickThroughToolbar;
 
 /**
  * Common base Activity for car apps that need to present a Drawer.
@@ -46,6 +50,12 @@
  *
  * <p>This class will take care of drawer toggling and display.
  *
+ * <p>This Activity also exposes the ability to have its toolbar optionally hide if any content
+ * in its main view is scrolled. Be default, this ability is turned off. Call
+ * {@link #setToolbarCollapsible()} to enable this behavior. Additionally, a user can set elevation
+ * on this toolbar by calling the appropriate {@link #setToolbarElevation(float)} method. There is
+ * elevation on the toolbar by default.
+ *
  * <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the
  * CarDrawerAdapter for the next level to
  * {@link CarDrawerController#pushAdapter(CarDrawerAdapter)}.
@@ -54,7 +64,21 @@
  * derivative.
  */
 public class CarDrawerActivity extends AppCompatActivity {
+    private static final int ANIMATION_DURATION_MS = 100;
+    private static final float DRAWER_OPEN_OFFSET = 0.25f;
+
     private CarDrawerController mDrawerController;
+    private AppBarLayout mAppBarLayout;
+
+    /**
+     * Whether or not the drawer is considered opened. This value is only set and unset when the
+     * drawer has passed the {@link #DRAWER_OPEN_OFFSET}.
+     */
+    private boolean mDrawerOpen;
+    private float mAppBarElevation;
+
+    private ClickThroughToolbar mToolbar;
+    private boolean mToolbarCollapsible;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -62,6 +86,10 @@
 
         setContentView(R.layout.car_drawer_activity);
 
+        mAppBarLayout = findViewById(R.id.appbar);
+        mAppBarLayout.setBackgroundColor(getThemeColorPrimary());
+        setToolbarElevation(getResources().getDimension(R.dimen.car_app_bar_default_elevation));
+
         DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
         ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
                 this /* activity */,
@@ -69,10 +97,10 @@
                 R.string.car_drawer_open,
                 R.string.car_drawer_close);
 
-        Toolbar toolbar = findViewById(R.id.car_toolbar);
-        setSupportActionBar(toolbar);
+        mToolbar = findViewById(R.id.car_toolbar);
+        setSupportActionBar(mToolbar);
 
-        mDrawerController = new CarDrawerController(toolbar, drawerLayout, drawerToggle);
+        mDrawerController = new CarDrawerController(mToolbar, drawerLayout, drawerToggle);
         CarDrawerAdapter rootAdapter = getRootAdapter();
         if (rootAdapter != null) {
             mDrawerController.setRootAdapter(rootAdapter);
@@ -80,6 +108,44 @@
 
         getSupportActionBar().setDisplayHomeAsUpEnabled(true);
         getSupportActionBar().setHomeButtonEnabled(true);
+
+        mDrawerController.addDrawerListener(new DrawerLayout.DrawerListener() {
+            private final int mClosedDrawerToolbarColor = getThemeColorPrimary();
+
+            @Override
+            public void onDrawerSlide(View view, float slideOffset) {
+                if (slideOffset >= DRAWER_OPEN_OFFSET) {
+                    // If this is the first time the drawer is considered open, then save the
+                    // value of the elevation to restore later.
+                    if (!mDrawerOpen) {
+                        mDrawerOpen = true;
+                        mAppBarElevation = mAppBarLayout.getElevation();
+                    }
+
+                    mAppBarLayout.setBackgroundColor(Color.TRANSPARENT);
+                    setToolbarElevation(0);
+                    setToolbarAlwaysShowInternal();
+                } else if (mDrawerOpen) {
+                    // Only reset the state of the AppBar if the drawer has reached the open state.
+                    mDrawerOpen = false;
+                    mAppBarLayout.setBackgroundColor(mClosedDrawerToolbarColor);
+                    setToolbarElevation(mAppBarElevation);
+
+                    if (mToolbarCollapsible) {
+                        setToolbarCollapsibleInternal();
+                    }
+                }
+            }
+
+            @Override
+            public void onDrawerOpened(View view) {}
+
+            @Override
+            public void onDrawerClosed(View view) {}
+
+            @Override
+            public void onDrawerStateChanged(int i) {}
+        });
     }
 
     /**
@@ -135,6 +201,85 @@
     }
 
     /**
+     * Sets whether clicks on the toolbar of this view will pass through to any views underneath it.
+     * By default, the toolbar will not allow clicks to pass through. This is the equivalent of
+     * passing {@code false} to this method.
+     *
+     * @param clickThrough {@code true} if clicks will pass through; {@code false} for the toolbar
+     *                     to eat all clicks.
+     */
+    public void setToolbarClickThrough(boolean clickThrough) {
+        mToolbar.setClickPassThrough(clickThrough);
+    }
+
+    /**
+     * Sets the elevation on the toolbar of this Activity.
+     *
+     * @param elevation The elevation to set.
+     */
+    public void setToolbarElevation(float elevation) {
+        // The AppBar's default animator needs to be set to null to manually change the elevation.
+        mAppBarLayout.setStateListAnimator(null);
+        mAppBarLayout.setElevation(elevation);
+    }
+
+    /**
+     * Sets the elevation of the toolbar and animate it from the current elevation value.
+     *
+     * @param elevation The elevation to set.
+     */
+    public void setToolbarElevationWithAnimation(float elevation) {
+        ValueAnimator elevationAnimator =
+                ValueAnimator.ofFloat(mAppBarLayout.getElevation(), elevation);
+        elevationAnimator
+                .setDuration(ANIMATION_DURATION_MS)
+                .addUpdateListener(animation -> setToolbarElevation(
+                        (float) animation.getAnimatedValue()));
+        elevationAnimator.start();
+    }
+
+    /**
+     * Sets the toolbar of this Activity as collapsible. When any content in the main view of the
+     * Activity is scrolled, the toolbar will collapse and show itself accordingly.
+     */
+    public void setToolbarCollapsible() {
+        mToolbarCollapsible = true;
+        setToolbarCollapsibleInternal();
+    }
+
+    /**
+     * An internal-use method that sets the toolbar as collapsible. This version of the method
+     * does not override {@link #mToolbarCollapsible}, and thus retains whatever value a user
+     * of this Activity has set.
+     */
+    private void setToolbarCollapsibleInternal() {
+        AppBarLayout.LayoutParams params =
+                (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
+        params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
+                | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
+    }
+
+    /**
+     * Sets the toolbar to always show even if content in the main view of the Activity has been
+     * scrolled. This is the default behavior.
+     */
+    public void setToolbarAlwaysShow() {
+        mToolbarCollapsible = false;
+        setToolbarAlwaysShowInternal();
+    }
+
+    /**
+     * An internal-use method that sets the toolbar to always show. This version of the method does
+     * not override {@link #mToolbarCollapsible}, and thus retains whatever value a user of this
+     * Activity has set.
+     */
+    private void setToolbarAlwaysShowInternal() {
+        AppBarLayout.LayoutParams params =
+                (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
+        params.setScrollFlags(0);
+    }
+
+    /**
      * Get the id of the main content Container which is a FrameLayout. Subclasses can add their own
      * content/fragments inside here.
      *
@@ -160,4 +305,14 @@
     public boolean onOptionsItemSelected(MenuItem item) {
         return mDrawerController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
     }
+
+    /**
+     * Returns the color that has been set as {@code colorPrimary} on the current Theme of this
+     * Activity.
+     */
+    private int getThemeColorPrimary() {
+        TypedValue value = new TypedValue();
+        getTheme().resolveAttribute(android.R.attr.colorPrimary, value, true);
+        return value.data;
+    }
 }
diff --git a/car/src/main/java/androidx/car/widget/ClickThroughToolbar.java b/car/src/main/java/androidx/car/widget/ClickThroughToolbar.java
new file mode 100644
index 0000000..ddfce77
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/ClickThroughToolbar.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 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.car.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.widget.Toolbar;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import androidx.car.R;
+
+/**
+ * A toolbar that optionally supports allowing clicks on it to pass through to any underlying views.
+ *
+ * <p>By default, the {@link Toolbar} eats all touches on it. This view will override
+ * {@link #onTouchEvent(MotionEvent)} and return {@code false} if configured to allow pass through.
+ */
+public class ClickThroughToolbar extends Toolbar {
+    private boolean mAllowClickPassThrough;
+
+    public ClickThroughToolbar(Context context) {
+        super(context);
+    }
+
+    public ClickThroughToolbar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initAttributes(context, attrs, 0 /* defStyleAttrs */);
+    }
+
+    public ClickThroughToolbar(Context context, AttributeSet attrs, int defStyleAttrs) {
+        super(context, attrs, defStyleAttrs);
+        initAttributes(context, attrs, defStyleAttrs);
+    }
+
+    private void initAttributes(Context context, AttributeSet attrs, int defStyleAttrs) {
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.ClickThroughToolbar, defStyleAttrs, 0 /* defStyleRes */);
+
+        mAllowClickPassThrough = a.getBoolean(R.styleable.ClickThroughToolbar_clickThrough, false);
+
+        a.recycle();
+    }
+
+    /**
+     * Whether or not clicks on this toolbar will pass through to any views that are underneath
+     * it. By default, this value is {@code false}.
+     *
+     * @param allowPassThrough {@code true} if clicks will pass through to an underlying view;
+     *                         {@code false} otherwise.
+     */
+    public void setClickPassThrough(boolean allowPassThrough) {
+        mAllowClickPassThrough = allowPassThrough;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mAllowClickPassThrough) {
+            return false;
+        }
+
+        return super.onTouchEvent(ev);
+    }
+}
diff --git a/car/src/main/java/androidx/car/widget/PagedListView.java b/car/src/main/java/androidx/car/widget/PagedListView.java
index a6811b1..654cba9 100644
--- a/car/src/main/java/androidx/car/widget/PagedListView.java
+++ b/car/src/main/java/androidx/car/widget/PagedListView.java
@@ -40,6 +40,7 @@
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 import androidx.car.R;
@@ -123,6 +124,10 @@
 
     private boolean mNeedsFocus;
 
+    @Gutter
+    private int mGutter;
+    private int mGutterSize;
+
     /**
      * Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to cap the number of
      * items.
@@ -245,12 +250,16 @@
                 new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
         mRecyclerView.setLayoutManager(layoutManager);
 
-        mSnapHelper = new PagedSnapHelper();
+        mSnapHelper = new PagedSnapHelper(context);
         mSnapHelper.attachToRecyclerView(mRecyclerView);
 
         mRecyclerView.setOnScrollListener(mRecyclerViewOnScrollListener);
         mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
 
+        int defaultGutterSize = getResources().getDimensionPixelSize(R.dimen.car_margin);
+        mGutterSize = a.getDimensionPixelSize(R.styleable.PagedListView_gutterSize,
+                defaultGutterSize);
+
         if (a.hasValue(R.styleable.PagedListView_gutter)) {
             int gutter = a.getInt(R.styleable.PagedListView_gutter, Gutter.BOTH);
             setGutter(gutter);
@@ -281,6 +290,12 @@
             mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing));
         }
 
+        int listContentTopMargin =
+                a.getDimensionPixelSize(R.styleable.PagedListView_listContentTopOffset, 0);
+        if (listContentTopMargin > 0) {
+            mRecyclerView.addItemDecoration(new TopOffsetDecoration(listContentTopMargin));
+        }
+
         // Set this to true so that this view consumes clicks events and views underneath
         // don't receive this click event. Without this it's possible to click places in the
         // view that don't capture the event, and as a result, elements visually hidden consume
@@ -333,6 +348,13 @@
             params.setMarginStart(0);
         }
 
+        if (a.hasValue(R.styleable.PagedListView_scrollBarContainerWidth)) {
+            int carMargin = getResources().getDimensionPixelSize(R.dimen.car_margin);
+            int scrollBarContainerWidth = a.getDimensionPixelSize(
+                    R.styleable.PagedListView_scrollBarContainerWidth, carMargin);
+            setScrollBarContainerWidth(scrollBarContainerWidth);
+        }
+
         setDayNightStyle(DayNightStyle.AUTO);
         a.recycle();
     }
@@ -360,20 +382,22 @@
     /**
      * Set the gutter to the specified value.
      *
-     * The gutter is the space to the start/end of the list view items and will be equal in size
+     * <p>The gutter is the space to the start/end of the list view items and will be equal in size
      * to the scroll bars. By default, there is a gutter to both the left and right of the list
      * view items, to account for the scroll bar.
      *
      * @param gutter A {@link Gutter} value that identifies which sides to apply the gutter to.
      */
     public void setGutter(@Gutter int gutter) {
+        mGutter = gutter;
+
         int startPadding = 0;
         int endPadding = 0;
-        if ((gutter & Gutter.START) != 0) {
-            startPadding = getResources().getDimensionPixelSize(R.dimen.car_margin);
+        if ((mGutter & Gutter.START) != 0) {
+            startPadding = mGutterSize;
         }
-        if ((gutter & Gutter.END) != 0) {
-            endPadding = getResources().getDimensionPixelSize(R.dimen.car_margin);
+        if ((mGutter & Gutter.END) != 0) {
+            endPadding = mGutterSize;
         }
         mRecyclerView.setPaddingRelative(startPadding, 0, endPadding, 0);
 
@@ -383,6 +407,32 @@
     }
 
     /**
+     * Sets the size of the gutter that appears at the start, end or both sizes of the items in
+     * the {@code PagedListView}.
+     *
+     * @param gutterSize The size of the gutter in pixels.
+     * @see #setGutter(int)
+     */
+    public void setGutterSize(int gutterSize) {
+        mGutterSize = gutterSize;
+
+        // Call setGutter to reset the gutter.
+        setGutter(mGutter);
+    }
+
+    /**
+     * Sets the width of the container that holds the scrollbar. The scrollbar will be centered
+     * within this width.
+     *
+     * @param width The width of the scrollbar container.
+     */
+    public void setScrollBarContainerWidth(int width) {
+        ViewGroup.LayoutParams layoutParams = mScrollBarView.getLayoutParams();
+        layoutParams.width = width;
+        mScrollBarView.requestLayout();
+    }
+
+    /**
      * Sets the top margin above the scroll bar. By default, this margin is 0.
      *
      * @param topMargin The top margin.
@@ -390,7 +440,33 @@
     public void setScrollBarTopMargin(int topMargin) {
         MarginLayoutParams params = (MarginLayoutParams) mScrollBarView.getLayoutParams();
         params.topMargin = topMargin;
-        requestLayout();
+        mScrollBarView.requestLayout();
+    }
+
+    /**
+     * Sets an offset above the first item in the {@code PagedListView}. This offset is scrollable
+     * with the contents of the list.
+     *
+     * @param offset The top offset to add.
+     */
+    public void setListContentTopOffset(int offset) {
+        TopOffsetDecoration existing = null;
+        for (int i = 0, count = mRecyclerView.getItemDecorationCount(); i < count; i++) {
+            RecyclerView.ItemDecoration itemDecoration = mRecyclerView.getItemDecorationAt(i);
+            if (itemDecoration instanceof TopOffsetDecoration) {
+                existing = (TopOffsetDecoration) itemDecoration;
+                break;
+            }
+        }
+
+        if (offset == 0 && existing != null) {
+            mRecyclerView.removeItemDecoration(existing);
+        } else if (existing == null) {
+            mRecyclerView.addItemDecoration(new TopOffsetDecoration(offset));
+        } else {
+            existing.setTopOffset(offset);
+        }
+        mRecyclerView.invalidateItemDecorations();
     }
 
     @NonNull
@@ -404,14 +480,15 @@
      * @param position The position in the list to scroll to.
      */
     public void scrollToPosition(int position) {
-        if (mRecyclerView.getLayoutManager() == null) {
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager == null) {
             return;
         }
 
-        PagedSmoothScroller smoothScroller = new PagedSmoothScroller(getContext());
+        RecyclerView.SmoothScroller smoothScroller = mSnapHelper.createScroller(layoutManager);
         smoothScroller.setTargetPosition(position);
 
-        mRecyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
+        layoutManager.startSmoothScroll(smoothScroller);
 
         // Sometimes #scrollToPosition doesn't change the scroll state so we need to make sure
         // the pagination arrows actually get updated. See b/15801119
@@ -1111,4 +1188,35 @@
             return mVisibilityManager != null && mVisibilityManager.shouldHideDivider(position);
         }
     }
+
+    /**
+     * A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will add a top offset
+     * to the first item in the RecyclerView it is added to.
+     */
+    private static class TopOffsetDecoration extends RecyclerView.ItemDecoration {
+        private int mTopOffset;
+
+        private TopOffsetDecoration(int topOffset) {
+            mTopOffset = topOffset;
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                RecyclerView.State state) {
+            super.getItemOffsets(outRect, view, parent, state);
+            int position = parent.getChildAdapterPosition(view);
+
+            // Only set the offset for the first item.
+            if (position == 0) {
+                outRect.top = mTopOffset;
+            }
+        }
+
+        /**
+         * @param topOffset sets spacing between each item.
+         */
+        public void setTopOffset(int topOffset) {
+            mTopOffset = topOffset;
+        }
+    }
 }
diff --git a/car/src/main/java/androidx/car/widget/PagedSnapHelper.java b/car/src/main/java/androidx/car/widget/PagedSnapHelper.java
index ad1c710..9bf1fb6 100644
--- a/car/src/main/java/androidx/car/widget/PagedSnapHelper.java
+++ b/car/src/main/java/androidx/car/widget/PagedSnapHelper.java
@@ -16,6 +16,7 @@
 
 package androidx.car.widget;
 
+import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.LinearSnapHelper;
@@ -36,6 +37,7 @@
      */
     private static final float VIEW_VISIBLE_THRESHOLD = 0.5f;
 
+    private final PagedSmoothScroller mSmoothScroller;
     private RecyclerView mRecyclerView;
 
     // Orientation helpers are lazily created per LayoutManager.
@@ -45,6 +47,10 @@
     @Nullable
     private OrientationHelper mHorizontalHelper;
 
+    public PagedSnapHelper(Context context) {
+        mSmoothScroller = new PagedSmoothScroller(context);
+    }
+
     @Override
     public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
             @NonNull View targetView) {
@@ -167,6 +173,20 @@
     }
 
     /**
+     * Returns a scroller specific to this {@code PagedSnapHelper}. This scroller is used for all
+     * smooth scrolling operations, including flings.
+     *
+     * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+     *                      {@link RecyclerView}.
+     *
+     * @return a {@link RecyclerView.SmoothScroller} which will handle the scrolling.
+     */
+    @Override
+    protected RecyclerView.SmoothScroller createScroller(RecyclerView.LayoutManager layoutManager) {
+        return mSmoothScroller;
+    }
+
+    /**
      * Calculate the estimated scroll distance in each direction given velocities on both axes.
      * This method will clamp the maximum scroll distance so that a single fling will never scroll
      * more than one page.
diff --git a/car/tests/src/androidx/car/widget/PagedListViewTest.java b/car/tests/src/androidx/car/widget/PagedListViewTest.java
index 67aa638..fe56a71 100644
--- a/car/tests/src/androidx/car/widget/PagedListViewTest.java
+++ b/car/tests/src/androidx/car/widget/PagedListViewTest.java
@@ -36,6 +36,7 @@
 import static org.junit.Assert.assertThat;
 
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
@@ -380,6 +381,175 @@
         onView(withId(R.id.paged_scroll_view)).check(matches(withTopMargin(topMargin)));
     }
 
+    @Test
+    public void testSetGutterNone() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        mPagedListView.setGutter(PagedListView.Gutter.NONE);
+
+        assertThat(mPagedListView.getRecyclerView().getPaddingStart(), is(equalTo(0)));
+        assertThat(mPagedListView.getRecyclerView().getPaddingEnd(), is(equalTo(0)));
+    }
+
+    @Test
+    public void testSetGutterStart() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        mPagedListView.setGutter(PagedListView.Gutter.START);
+
+        Resources res = InstrumentationRegistry.getContext().getResources();
+        int gutterSize = res.getDimensionPixelSize(R.dimen.car_margin);
+
+        assertThat(mPagedListView.getRecyclerView().getPaddingStart(), is(equalTo(gutterSize)));
+        assertThat(mPagedListView.getRecyclerView().getPaddingEnd(), is(equalTo(0)));
+    }
+
+    @Test
+    public void testSetGutterEnd() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        mPagedListView.setGutter(PagedListView.Gutter.END);
+
+        Resources res = InstrumentationRegistry.getContext().getResources();
+        int gutterSize = res.getDimensionPixelSize(R.dimen.car_margin);
+
+        assertThat(mPagedListView.getRecyclerView().getPaddingStart(), is(equalTo(0)));
+        assertThat(mPagedListView.getRecyclerView().getPaddingEnd(), is(equalTo(gutterSize)));
+    }
+
+    @Test
+    public void testSetGutterBoth() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        mPagedListView.setGutter(PagedListView.Gutter.BOTH);
+
+        Resources res = InstrumentationRegistry.getContext().getResources();
+        int gutterSize = res.getDimensionPixelSize(R.dimen.car_margin);
+
+        assertThat(mPagedListView.getRecyclerView().getPaddingStart(), is(equalTo(gutterSize)));
+        assertThat(mPagedListView.getRecyclerView().getPaddingEnd(), is(equalTo(gutterSize)));
+    }
+
+    @Test
+    public void testSetGutterSizeNone() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        mPagedListView.setGutter(PagedListView.Gutter.NONE);
+        mPagedListView.setGutterSize(120);
+
+        assertThat(mPagedListView.getRecyclerView().getPaddingStart(), is(equalTo(0)));
+        assertThat(mPagedListView.getRecyclerView().getPaddingEnd(), is(equalTo(0)));
+    }
+
+    @Test
+    public void testSetGutterSizeStart() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        mPagedListView.setGutter(PagedListView.Gutter.START);
+
+        int gutterSize = 120;
+        mPagedListView.setGutterSize(gutterSize);
+
+        assertThat(mPagedListView.getRecyclerView().getPaddingStart(), is(equalTo(gutterSize)));
+        assertThat(mPagedListView.getRecyclerView().getPaddingEnd(), is(equalTo(0)));
+    }
+
+    @Test
+    public void testSetGutterSizeEnd() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        mPagedListView.setGutter(PagedListView.Gutter.END);
+
+        int gutterSize = 120;
+        mPagedListView.setGutterSize(gutterSize);
+
+        assertThat(mPagedListView.getRecyclerView().getPaddingStart(), is(equalTo(0)));
+        assertThat(mPagedListView.getRecyclerView().getPaddingEnd(), is(equalTo(gutterSize)));
+    }
+
+    @Test
+    public void testSetGutterSizeBoth() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        mPagedListView.setGutter(PagedListView.Gutter.BOTH);
+
+        int gutterSize = 120;
+        mPagedListView.setGutterSize(gutterSize);
+
+        assertThat(mPagedListView.getRecyclerView().getPaddingStart(), is(equalTo(gutterSize)));
+        assertThat(mPagedListView.getRecyclerView().getPaddingEnd(), is(equalTo(gutterSize)));
+    }
+
+    @Test
+    public void setDefaultScrollBarContainerWidth() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        Resources res = InstrumentationRegistry.getContext().getResources();
+        int defaultWidth = res.getDimensionPixelSize(R.dimen.car_margin);
+
+        onView(withId(R.id.paged_scroll_view)).check(matches(withWidth(defaultWidth)));
+    }
+
+    @Test
+    public void testSetScrollBarContainerWidth() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // Just need enough items to ensure the scroll bar is showing.
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        int scrollBarContainerWidth = 120;
+        mPagedListView.setScrollBarContainerWidth(scrollBarContainerWidth);
+
+        onView(withId(R.id.paged_scroll_view)).check(matches(withWidth(scrollBarContainerWidth)));
+    }
+
     private static String itemText(int index) {
         return "Data " + index;
     }
@@ -512,4 +682,24 @@
             }
         };
     }
+
+    /**
+     * Returns a matcher that matches {@link View}s that have the given width.
+     *
+     * @param width The width to match to.
+     */
+    @NonNull
+    public static Matcher<View> withWidth(int width) {
+        return new TypeSafeMatcher<View>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("with width: " + width);
+            }
+
+            @Override
+            public boolean matchesSafely(View view) {
+                return width == view.getLayoutParams().width;
+            }
+        };
+    }
 }
diff --git a/compat/api/current.txt b/compat/api/current.txt
index 9745887..37fdd22 100644
--- a/compat/api/current.txt
+++ b/compat/api/current.txt
@@ -204,6 +204,7 @@
     field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
     field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
     field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
+    field public static final java.lang.String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
     field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
     field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
     field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
@@ -257,7 +258,19 @@
     method public android.os.Bundle getExtras();
     method public int getIcon();
     method public android.support.v4.app.RemoteInput[] getRemoteInputs();
+    method public int getSemanticAction();
+    method public boolean getShowsUserInterface();
     method public java.lang.CharSequence getTitle();
+    field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
+    field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
+    field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
+    field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
+    field public static final int SEMANTIC_ACTION_MUTE = 6; // 0x6
+    field public static final int SEMANTIC_ACTION_NONE = 0; // 0x0
+    field public static final int SEMANTIC_ACTION_REPLY = 1; // 0x1
+    field public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; // 0x9
+    field public static final int SEMANTIC_ACTION_THUMBS_UP = 8; // 0x8
+    field public static final int SEMANTIC_ACTION_UNMUTE = 7; // 0x7
     field public android.app.PendingIntent actionIntent;
     field public int icon;
     field public java.lang.CharSequence title;
@@ -272,12 +285,17 @@
     method public android.support.v4.app.NotificationCompat.Action.Builder extend(android.support.v4.app.NotificationCompat.Action.Extender);
     method public android.os.Bundle getExtras();
     method public android.support.v4.app.NotificationCompat.Action.Builder setAllowGeneratedReplies(boolean);
+    method public android.support.v4.app.NotificationCompat.Action.Builder setSemanticAction(int);
+    method public android.support.v4.app.NotificationCompat.Action.Builder setShowsUserInterface(boolean);
   }
 
   public static abstract interface NotificationCompat.Action.Extender {
     method public abstract android.support.v4.app.NotificationCompat.Action.Builder extend(android.support.v4.app.NotificationCompat.Action.Builder);
   }
 
+  public static abstract class NotificationCompat.Action.SemanticAction implements java.lang.annotation.Annotation {
+  }
+
   public static final class NotificationCompat.Action.WearableExtender implements android.support.v4.app.NotificationCompat.Action.Extender {
     ctor public NotificationCompat.Action.WearableExtender();
     ctor public NotificationCompat.Action.WearableExtender(android.support.v4.app.NotificationCompat.Action);
@@ -432,7 +450,9 @@
     method public java.lang.CharSequence getConversationTitle();
     method public java.util.List<android.support.v4.app.NotificationCompat.MessagingStyle.Message> getMessages();
     method public java.lang.CharSequence getUserDisplayName();
+    method public boolean isGroupConversation();
     method public android.support.v4.app.NotificationCompat.MessagingStyle setConversationTitle(java.lang.CharSequence);
+    method public android.support.v4.app.NotificationCompat.MessagingStyle setGroupConversation(boolean);
     field public static final int MAXIMUM_RETAINED_MESSAGES = 25; // 0x19
   }
 
@@ -1191,6 +1211,7 @@
     method public E get(long, E);
     method public int indexOfKey(long);
     method public int indexOfValue(E);
+    method public boolean isEmpty();
     method public long keyAt(int);
     method public void put(long, E);
     method public void remove(long);
@@ -1292,6 +1313,7 @@
     method public E get(int, E);
     method public int indexOfKey(int);
     method public int indexOfValue(E);
+    method public boolean isEmpty();
     method public int keyAt(int);
     method public void put(int, E);
     method public void remove(int);
@@ -1792,6 +1814,7 @@
 
   public final class ViewConfigurationCompat {
     method public static float getScaledHorizontalScrollFactor(android.view.ViewConfiguration, android.content.Context);
+    method public static int getScaledHoverSlop(android.view.ViewConfiguration);
     method public static deprecated int getScaledPagingTouchSlop(android.view.ViewConfiguration);
     method public static float getScaledVerticalScrollFactor(android.view.ViewConfiguration, android.content.Context);
     method public static deprecated boolean hasPermanentMenuKey(android.view.ViewConfiguration);
diff --git a/compat/res-public/values/public_attrs.xml b/compat/res-public/values/public_attrs.xml
index e45f8c2..38d858e 100644
--- a/compat/res-public/values/public_attrs.xml
+++ b/compat/res-public/values/public_attrs.xml
@@ -26,4 +26,6 @@
      <public type="attr" name="fontStyle"/>
      <public type="attr" name="font"/>
      <public type="attr" name="fontWeight"/>
+     <public type="attr" name="fontVariationSettings"/>
+     <public type="attr" name="ttcIndex"/>
 </resources>
diff --git a/compat/res/values/attrs.xml b/compat/res/values/attrs.xml
index 04d7690..1a721a8 100644
--- a/compat/res/values/attrs.xml
+++ b/compat/res/values/attrs.xml
@@ -79,10 +79,19 @@
          common values are 400 for regular weight and 700 for bold weight. If unspecified, the value
          in the font's header tables will be used. -->
         <attr name="fontWeight" format="integer" />
-
+        <!-- The variation settings to be applied to the font. The string should be in the following
+         format: "'tag1' value1, 'tag2' value2, ...". If the default variation settings should be
+         used, or the font used does not support variation settings, this attribute needs not be
+         specified. -->
+        <attr name="fontVariationSettings" format="string" />
+        <!-- The index of the font in the tcc font file. If the font file referenced is not in the
+        tcc format, this attribute needs not be specified. -->
+        <attr name="ttcIndex" format="integer" />
         <!-- References to the framework attrs -->
         <attr name="android:fontStyle" />
         <attr name="android:font" />
         <attr name="android:fontWeight" />
+        <attr name="android:fontVariationSettings" />
+        <attr name="android:ttcIndex" />
     </declare-styleable>
 </resources>
diff --git a/compat/src/main/java/android/support/v13/view/DragAndDropPermissionsCompat.java b/compat/src/main/java/android/support/v13/view/DragAndDropPermissionsCompat.java
index 13ed203..5fe61da 100644
--- a/compat/src/main/java/android/support/v13/view/DragAndDropPermissionsCompat.java
+++ b/compat/src/main/java/android/support/v13/view/DragAndDropPermissionsCompat.java
@@ -20,57 +20,16 @@
 
 import android.app.Activity;
 import android.os.Build;
-import android.support.annotation.RequiresApi;
+import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.view.DragAndDropPermissions;
 import android.view.DragEvent;
 
 /**
- * Helper for accessing features in {@link android.view.DragAndDropPermissions}
- * introduced after API level 13 in a backwards compatible fashion.
+ * Helper for accessing features in {@link android.view.DragAndDropPermissions} a backwards
+ * compatible fashion.
  */
 public final class DragAndDropPermissionsCompat {
-
-    interface DragAndDropPermissionsCompatImpl {
-        Object request(Activity activity, DragEvent dragEvent);
-        void release(Object dragAndDropPermissions);
-    }
-
-    static class BaseDragAndDropPermissionsCompatImpl implements DragAndDropPermissionsCompatImpl {
-        @Override
-        public Object request(Activity activity, DragEvent dragEvent) {
-            return null;
-        }
-
-        @Override
-        public void release(Object dragAndDropPermissions) {
-            // no-op
-        }
-    }
-
-    @RequiresApi(24)
-    static class Api24DragAndDropPermissionsCompatImpl
-            extends BaseDragAndDropPermissionsCompatImpl {
-        @Override
-        public Object request(Activity activity, DragEvent dragEvent) {
-            return activity.requestDragAndDropPermissions(dragEvent);
-        }
-
-        @Override
-        public void release(Object dragAndDropPermissions) {
-            ((DragAndDropPermissions) dragAndDropPermissions).release();
-        }
-    }
-
-    private static DragAndDropPermissionsCompatImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 24) {
-            IMPL = new Api24DragAndDropPermissionsCompatImpl();
-        } else {
-            IMPL = new BaseDragAndDropPermissionsCompatImpl();
-        }
-    }
-
     private Object mDragAndDropPermissions;
 
     private DragAndDropPermissionsCompat(Object dragAndDropPermissions) {
@@ -79,18 +38,24 @@
 
     /** @hide */
     @RestrictTo(LIBRARY_GROUP)
+    @Nullable
     public static DragAndDropPermissionsCompat request(Activity activity, DragEvent dragEvent) {
-        Object dragAndDropPermissions = IMPL.request(activity, dragEvent);
-        if (dragAndDropPermissions != null) {
-            return new DragAndDropPermissionsCompat(dragAndDropPermissions);
+        if (Build.VERSION.SDK_INT >= 24) {
+            DragAndDropPermissions dragAndDropPermissions =
+                    activity.requestDragAndDropPermissions(dragEvent);
+            if (dragAndDropPermissions != null) {
+                return new DragAndDropPermissionsCompat(dragAndDropPermissions);
+            }
         }
         return null;
     }
 
-    /*
+    /**
      * Revoke the permission grant explicitly.
      */
     public void release() {
-        IMPL.release(mDragAndDropPermissions);
+        if (Build.VERSION.SDK_INT >= 24) {
+            ((DragAndDropPermissions) mDragAndDropPermissions).release();
+        }
     }
 }
diff --git a/compat/src/main/java/android/support/v13/view/DragStartHelper.java b/compat/src/main/java/android/support/v13/view/DragStartHelper.java
index 85bc2f3..f8aed92 100644
--- a/compat/src/main/java/android/support/v13/view/DragStartHelper.java
+++ b/compat/src/main/java/android/support/v13/view/DragStartHelper.java
@@ -69,8 +69,8 @@
  * </pre>
  */
 public class DragStartHelper {
-    final private View mView;
-    final private OnDragStartListener mListener;
+    private final View mView;
+    private final OnDragStartListener mListener;
 
     private int mLastTouchX, mLastTouchY;
     private boolean mDragging;
diff --git a/compat/src/main/java/android/support/v13/view/inputmethod/EditorInfoCompat.java b/compat/src/main/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
index 92743c2..309877d 100644
--- a/compat/src/main/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
+++ b/compat/src/main/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
@@ -16,7 +16,6 @@
 
 package android.support.v13.view.inputmethod;
 
-import android.support.annotation.RequiresApi;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
@@ -24,8 +23,7 @@
 import android.view.inputmethod.EditorInfo;
 
 /**
- * Helper for accessing features in {@link EditorInfo} introduced after API level 13 in a backwards
- * compatible fashion.
+ * Helper for accessing features in {@link EditorInfo} in a backwards compatible fashion.
  */
 public final class EditorInfoCompat {
 
@@ -69,63 +67,10 @@
      */
     public static final int IME_FLAG_FORCE_ASCII = 0x80000000;
 
-    private interface EditorInfoCompatImpl {
-        void setContentMimeTypes(@NonNull EditorInfo editorInfo,
-                @Nullable String[] contentMimeTypes);
-        @NonNull
-        String[] getContentMimeTypes(@NonNull EditorInfo editorInfo);
-    }
-
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
-    private static final class EditorInfoCompatBaseImpl implements EditorInfoCompatImpl {
-        private static String CONTENT_MIME_TYPES_KEY =
-                "android.support.v13.view.inputmethod.EditorInfoCompat.CONTENT_MIME_TYPES";
-
-        @Override
-        public void setContentMimeTypes(@NonNull EditorInfo editorInfo,
-                @Nullable String[] contentMimeTypes) {
-            if (editorInfo.extras == null) {
-                editorInfo.extras = new Bundle();
-            }
-            editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_KEY, contentMimeTypes);
-        }
-
-        @NonNull
-        @Override
-        public String[] getContentMimeTypes(@NonNull EditorInfo editorInfo) {
-            if (editorInfo.extras == null) {
-                return EMPTY_STRING_ARRAY;
-            }
-            String[] result = editorInfo.extras.getStringArray(CONTENT_MIME_TYPES_KEY);
-            return result != null ? result : EMPTY_STRING_ARRAY;
-        }
-    }
-
-    @RequiresApi(25)
-    private static final class EditorInfoCompatApi25Impl implements EditorInfoCompatImpl {
-        @Override
-        public void setContentMimeTypes(@NonNull EditorInfo editorInfo,
-                @Nullable String[] contentMimeTypes) {
-            editorInfo.contentMimeTypes = contentMimeTypes;
-        }
-
-        @NonNull
-        @Override
-        public String[] getContentMimeTypes(@NonNull EditorInfo editorInfo) {
-            final String[] result = editorInfo.contentMimeTypes;
-            return result != null ? result : EMPTY_STRING_ARRAY;
-        }
-    }
-
-    private static final EditorInfoCompatImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 25) {
-            IMPL = new EditorInfoCompatApi25Impl();
-        } else {
-            IMPL = new EditorInfoCompatBaseImpl();
-        }
-    }
+    private static final String CONTENT_MIME_TYPES_KEY =
+            "android.support.v13.view.inputmethod.EditorInfoCompat.CONTENT_MIME_TYPES";
 
     /**
      * Sets MIME types that can be accepted by the target editor if the IME calls
@@ -140,7 +85,14 @@
      */
     public static void setContentMimeTypes(@NonNull EditorInfo editorInfo,
             @Nullable String[] contentMimeTypes) {
-        IMPL.setContentMimeTypes(editorInfo, contentMimeTypes);
+        if (Build.VERSION.SDK_INT >= 25) {
+            editorInfo.contentMimeTypes = contentMimeTypes;
+        } else {
+            if (editorInfo.extras == null) {
+                editorInfo.extras = new Bundle();
+            }
+            editorInfo.extras.putStringArray(CONTENT_MIME_TYPES_KEY, contentMimeTypes);
+        }
     }
 
     /**
@@ -155,7 +107,16 @@
      */
     @NonNull
     public static String[] getContentMimeTypes(EditorInfo editorInfo) {
-        return IMPL.getContentMimeTypes(editorInfo);
+        if (Build.VERSION.SDK_INT >= 25) {
+            final String[] result = editorInfo.contentMimeTypes;
+            return result != null ? result : EMPTY_STRING_ARRAY;
+        } else {
+            if (editorInfo.extras == null) {
+                return EMPTY_STRING_ARRAY;
+            }
+            String[] result = editorInfo.extras.getStringArray(CONTENT_MIME_TYPES_KEY);
+            return result != null ? result : EMPTY_STRING_ARRAY;
+        }
     }
 
 }
diff --git a/compat/src/main/java/android/support/v13/view/inputmethod/InputConnectionCompat.java b/compat/src/main/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
index 5999575..d77389b 100644
--- a/compat/src/main/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
+++ b/compat/src/main/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
@@ -16,7 +16,6 @@
 
 package android.support.v13.view.inputmethod;
 
-import android.support.annotation.RequiresApi;
 import android.content.ClipDescription;
 import android.net.Uri;
 import android.os.Build;
@@ -36,138 +35,50 @@
  */
 public final class InputConnectionCompat {
 
-    private interface InputConnectionCompatImpl {
-        boolean commitContent(@NonNull InputConnection inputConnection,
-                @NonNull InputContentInfoCompat inputContentInfo, int flags, @Nullable Bundle opts);
+    private static final String COMMIT_CONTENT_ACTION =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT";
+    private static final String COMMIT_CONTENT_CONTENT_URI_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_URI";
+    private static final String COMMIT_CONTENT_DESCRIPTION_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION";
+    private static final String COMMIT_CONTENT_LINK_URI_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI";
+    private static final String COMMIT_CONTENT_OPTS_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_OPTS";
+    private static final String COMMIT_CONTENT_FLAGS_KEY =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS";
+    private static final String COMMIT_CONTENT_RESULT_RECEIVER =
+            "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_RESULT_RECEIVER";
 
-        @NonNull
-        InputConnection createWrapper(@NonNull InputConnection ic,
-                @NonNull EditorInfo editorInfo, @NonNull OnCommitContentListener callback);
-    }
-
-    static final class InputContentInfoCompatBaseImpl implements InputConnectionCompatImpl {
-
-        private static String COMMIT_CONTENT_ACTION =
-                "android.support.v13.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT";
-        private static String COMMIT_CONTENT_CONTENT_URI_KEY =
-                "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_URI";
-        private static String COMMIT_CONTENT_DESCRIPTION_KEY =
-                "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_DESCRIPTION";
-        private static String COMMIT_CONTENT_LINK_URI_KEY =
-                "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_LINK_URI";
-        private static String COMMIT_CONTENT_OPTS_KEY =
-                "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_OPTS";
-        private static String COMMIT_CONTENT_FLAGS_KEY =
-                "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_FLAGS";
-        private static String COMMIT_CONTENT_RESULT_RECEIVER =
-                "android.support.v13.view.inputmethod.InputConnectionCompat.CONTENT_RESULT_RECEIVER";
-
-        @Override
-        public boolean commitContent(@NonNull InputConnection inputConnection,
-                @NonNull InputContentInfoCompat inputContentInfo, int flags,
-                @Nullable Bundle opts) {
-            final Bundle params = new Bundle();
-            params.putParcelable(COMMIT_CONTENT_CONTENT_URI_KEY, inputContentInfo.getContentUri());
-            params.putParcelable(COMMIT_CONTENT_DESCRIPTION_KEY, inputContentInfo.getDescription());
-            params.putParcelable(COMMIT_CONTENT_LINK_URI_KEY, inputContentInfo.getLinkUri());
-            params.putInt(COMMIT_CONTENT_FLAGS_KEY, flags);
-            params.putParcelable(COMMIT_CONTENT_OPTS_KEY, opts);
-            // TODO: Support COMMIT_CONTENT_RESULT_RECEIVER.
-            return inputConnection.performPrivateCommand(COMMIT_CONTENT_ACTION, params);
+    static boolean handlePerformPrivateCommand(
+            @Nullable String action,
+            @NonNull Bundle data,
+            @NonNull OnCommitContentListener onCommitContentListener) {
+        if (!TextUtils.equals(COMMIT_CONTENT_ACTION, action)) {
+            return false;
         }
-
-        @NonNull
-        @Override
-        public InputConnection createWrapper(@NonNull InputConnection ic,
-                @NonNull EditorInfo editorInfo,
-                @NonNull OnCommitContentListener onCommitContentListener) {
-            String[] contentMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);
-            if (contentMimeTypes.length == 0) {
-                return ic;
+        if (data == null) {
+            return false;
+        }
+        ResultReceiver resultReceiver = null;
+        boolean result = false;
+        try {
+            resultReceiver = data.getParcelable(COMMIT_CONTENT_RESULT_RECEIVER);
+            final Uri contentUri = data.getParcelable(COMMIT_CONTENT_CONTENT_URI_KEY);
+            final ClipDescription description = data.getParcelable(
+                    COMMIT_CONTENT_DESCRIPTION_KEY);
+            final Uri linkUri = data.getParcelable(COMMIT_CONTENT_LINK_URI_KEY);
+            final int flags = data.getInt(COMMIT_CONTENT_FLAGS_KEY);
+            final Bundle opts = data.getParcelable(COMMIT_CONTENT_OPTS_KEY);
+            final InputContentInfoCompat inputContentInfo =
+                    new InputContentInfoCompat(contentUri, description, linkUri);
+            result = onCommitContentListener.onCommitContent(inputContentInfo, flags, opts);
+        } finally {
+            if (resultReceiver != null) {
+                resultReceiver.send(result ? 1 : 0, null);
             }
-            final OnCommitContentListener listener = onCommitContentListener;
-            return new InputConnectionWrapper(ic, false /* mutable */) {
-                @Override
-                public boolean performPrivateCommand(String action, Bundle data) {
-                    if (InputContentInfoCompatBaseImpl.handlePerformPrivateCommand(action, data,
-                            listener)) {
-                        return true;
-                    }
-                    return super.performPrivateCommand(action, data);
-                }
-            };
         }
-
-        static boolean handlePerformPrivateCommand(
-                @Nullable String action,
-                @NonNull Bundle data,
-                @NonNull OnCommitContentListener onCommitContentListener) {
-            if (!TextUtils.equals(COMMIT_CONTENT_ACTION, action)) {
-                return false;
-            }
-            if (data == null) {
-                return false;
-            }
-            ResultReceiver resultReceiver = null;
-            boolean result = false;
-            try {
-                resultReceiver = data.getParcelable(COMMIT_CONTENT_RESULT_RECEIVER);
-                final Uri contentUri = data.getParcelable(COMMIT_CONTENT_CONTENT_URI_KEY);
-                final ClipDescription description = data.getParcelable(
-                        COMMIT_CONTENT_DESCRIPTION_KEY);
-                final Uri linkUri = data.getParcelable(COMMIT_CONTENT_LINK_URI_KEY);
-                final int flags = data.getInt(COMMIT_CONTENT_FLAGS_KEY);
-                final Bundle opts = data.getParcelable(COMMIT_CONTENT_OPTS_KEY);
-                final InputContentInfoCompat inputContentInfo =
-                        new InputContentInfoCompat(contentUri, description, linkUri);
-                result = onCommitContentListener.onCommitContent(inputContentInfo, flags, opts);
-            } finally {
-                if (resultReceiver != null) {
-                    resultReceiver.send(result ? 1 : 0, null);
-                }
-            }
-            return result;
-        }
-    }
-
-    @RequiresApi(25)
-    private static final class InputContentInfoCompatApi25Impl
-            implements InputConnectionCompatImpl {
-        @Override
-        public boolean commitContent(@NonNull InputConnection inputConnection,
-                @NonNull InputContentInfoCompat inputContentInfo, int flags,
-                @Nullable Bundle opts) {
-            return inputConnection.commitContent((InputContentInfo) inputContentInfo.unwrap(),
-                    flags, opts);
-        }
-
-        @Nullable
-        @Override
-        public InputConnection createWrapper(
-                @Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo,
-                @Nullable OnCommitContentListener onCommitContentListener) {
-            final OnCommitContentListener listener = onCommitContentListener;
-            return new InputConnectionWrapper(inputConnection, false /* mutable */) {
-                @Override
-                public boolean commitContent(InputContentInfo inputContentInfo, int flags,
-                        Bundle opts) {
-                    if (listener.onCommitContent(InputContentInfoCompat.wrap(inputContentInfo),
-                            flags, opts)) {
-                        return true;
-                    }
-                    return super.commitContent(inputContentInfo, flags, opts);
-                }
-            };
-        }
-    }
-
-    private static final InputConnectionCompatImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 25) {
-            IMPL = new InputContentInfoCompatApi25Impl();
-        } else {
-            IMPL = new InputContentInfoCompatBaseImpl();
-        }
+        return result;
     }
 
     /**
@@ -196,7 +107,19 @@
             return false;
         }
 
-        return IMPL.commitContent(inputConnection, inputContentInfo, flags, opts);
+        if (Build.VERSION.SDK_INT >= 25) {
+            return inputConnection.commitContent(
+                    (InputContentInfo) inputContentInfo.unwrap(), flags, opts);
+        } else {
+            final Bundle params = new Bundle();
+            params.putParcelable(COMMIT_CONTENT_CONTENT_URI_KEY, inputContentInfo.getContentUri());
+            params.putParcelable(COMMIT_CONTENT_DESCRIPTION_KEY, inputContentInfo.getDescription());
+            params.putParcelable(COMMIT_CONTENT_LINK_URI_KEY, inputContentInfo.getLinkUri());
+            params.putInt(COMMIT_CONTENT_FLAGS_KEY, flags);
+            params.putParcelable(COMMIT_CONTENT_OPTS_KEY, opts);
+            // TODO: Support COMMIT_CONTENT_RESULT_RECEIVER.
+            return inputConnection.performPrivateCommand(COMMIT_CONTENT_ACTION, params);
+        }
     }
 
     /**
@@ -276,7 +199,35 @@
         if (onCommitContentListener == null) {
             throw new IllegalArgumentException("onCommitContentListener must be non-null");
         }
-        return IMPL.createWrapper(inputConnection, editorInfo, onCommitContentListener);
+        if (Build.VERSION.SDK_INT >= 25) {
+            final OnCommitContentListener listener = onCommitContentListener;
+            return new InputConnectionWrapper(inputConnection, false /* mutable */) {
+                @Override
+                public boolean commitContent(InputContentInfo inputContentInfo, int flags,
+                        Bundle opts) {
+                    if (listener.onCommitContent(InputContentInfoCompat.wrap(inputContentInfo),
+                            flags, opts)) {
+                        return true;
+                    }
+                    return super.commitContent(inputContentInfo, flags, opts);
+                }
+            };
+        } else {
+            String[] contentMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);
+            if (contentMimeTypes.length == 0) {
+                return inputConnection;
+            }
+            final OnCommitContentListener listener = onCommitContentListener;
+            return new InputConnectionWrapper(inputConnection, false /* mutable */) {
+                @Override
+                public boolean performPrivateCommand(String action, Bundle data) {
+                    if (InputConnectionCompat.handlePerformPrivateCommand(action, data, listener)) {
+                        return true;
+                    }
+                    return super.performPrivateCommand(action, data);
+                }
+            };
+        }
     }
 
 }
diff --git a/compat/src/main/java/android/support/v4/app/ActivityCompat.java b/compat/src/main/java/android/support/v4/app/ActivityCompat.java
index 9d15be1..32355e7 100644
--- a/compat/src/main/java/android/support/v4/app/ActivityCompat.java
+++ b/compat/src/main/java/android/support/v4/app/ActivityCompat.java
@@ -538,6 +538,7 @@
      * URIs. {@code null} if no content URIs are associated with the event or if permissions could
      * not be granted.
      */
+    @Nullable
     public static DragAndDropPermissionsCompat requestDragAndDropPermissions(Activity activity,
             DragEvent dragEvent) {
         return DragAndDropPermissionsCompat.request(activity, dragEvent);
diff --git a/compat/src/main/java/android/support/v4/app/NotificationCompat.java b/compat/src/main/java/android/support/v4/app/NotificationCompat.java
index b3f0d32..5a9a716 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationCompat.java
@@ -404,6 +404,12 @@
     public static final String EXTRA_MESSAGES = "android.messages";
 
     /**
+     * Notification key: whether the {@link NotificationCompat.MessagingStyle} notification
+     * represents a group conversation.
+     */
+    public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
+
+    /**
      * Keys into the {@link #getExtras} Bundle: the audio contents of this notification.
      *
      * This is for use when rendering the notification on an audio-focused interface;
@@ -2083,8 +2089,9 @@
         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
 
         CharSequence mUserDisplayName;
-        CharSequence mConversationTitle;
+        @Nullable CharSequence mConversationTitle;
         List<Message> mMessages = new ArrayList<>();
+        boolean mIsGroupConversation;
 
         MessagingStyle() {
         }
@@ -2107,20 +2114,27 @@
         }
 
         /**
-         * Sets the title to be displayed on this conversation. This should only be used for
-         * group messaging and left unset for one-on-one conversations.
-         * @param conversationTitle Title displayed for this conversation.
-         * @return this object for method chaining.
+         * Sets the title to be displayed on this conversation. May be set to {@code null}.
+         *
+         * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
+         * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
+         * conversation title to a non-null value will make {@link #isGroupConversation()} return
+         * {@code true} and passing {@code null} will make it return {@code false}. In
+         * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
+         * to set group conversation status.
+         *
+         * @param conversationTitle Title displayed for this conversation
+         * @return this object for method chaining
          */
-        public MessagingStyle setConversationTitle(CharSequence conversationTitle) {
+        public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
             mConversationTitle = conversationTitle;
             return this;
         }
 
         /**
-         * Return the title to be displayed on this conversation. Can be <code>null</code> and
-         * should be for one-on-one conversations
+         * Return the title to be displayed on this conversation. Can be {@code null}.
          */
+        @Nullable
         public CharSequence getConversationTitle() {
             return mConversationTitle;
         }
@@ -2169,6 +2183,42 @@
         }
 
         /**
+         * Sets whether this conversation notification represents a group.
+         * @param isGroupConversation {@code true} if the conversation represents a group,
+         * {@code false} otherwise.
+         * @return this object for method chaining
+         */
+        public MessagingStyle setGroupConversation(boolean isGroupConversation) {
+            mIsGroupConversation = isGroupConversation;
+            return this;
+        }
+
+        /**
+         * Returns {@code true} if this notification represents a group conversation, otherwise
+         * {@code false}.
+         *
+         * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
+         * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
+         * not the conversation title is set; returning {@code true} if the conversation title is
+         * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
+         * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
+         * named, non-group conversations.
+         *
+         * @see #setConversationTitle(CharSequence)
+         */
+        public boolean isGroupConversation() {
+            // When target SDK version is < P, a non-null conversation title dictates if this is
+            // as group conversation.
+            if (mBuilder != null
+                    && mBuilder.mContext.getApplicationInfo().targetSdkVersion
+                    < Build.VERSION_CODES.P) {
+                return mConversationTitle != null;
+            }
+
+            return mIsGroupConversation;
+        }
+
+        /**
          * Retrieves a {@link MessagingStyle} from a {@link Notification}, enabling an application
          * that has set a {@link MessagingStyle} using {@link NotificationCompat} or
          * {@link android.app.Notification.Builder} to send messaging information to another
@@ -2316,6 +2366,7 @@
             if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
                     Message.getBundleArrayForMessages(mMessages));
             }
+            extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
         }
 
         /**
@@ -2331,6 +2382,7 @@
             if (parcelables != null) {
                 mMessages = Message.getMessagesFromBundleArray(parcelables);
             }
+            mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
         }
 
         public static final class Message {
@@ -2744,6 +2796,64 @@
      * to attach actions.
      */
     public static class Action {
+        /**
+         * {@link SemanticAction}: No semantic action defined.
+         */
+        public static final int SEMANTIC_ACTION_NONE = 0;
+
+        /**
+         * {@link SemanticAction}: Reply to a conversation, chat, group, or wherever replies
+         * may be appropriate.
+         */
+        public static final int SEMANTIC_ACTION_REPLY = 1;
+
+        /**
+         * {@link SemanticAction}: Mark content as read.
+         */
+        public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
+
+        /**
+         * {@link SemanticAction}: Mark content as unread.
+         */
+        public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
+
+        /**
+         * {@link SemanticAction}: Delete the content associated with the notification. This
+         * could mean deleting an email, message, etc.
+         */
+        public static final int SEMANTIC_ACTION_DELETE = 4;
+
+        /**
+         * {@link SemanticAction}: Archive the content associated with the notification. This
+         * could mean archiving an email, message, etc.
+         */
+        public static final int SEMANTIC_ACTION_ARCHIVE = 5;
+
+        /**
+         * {@link SemanticAction}: Mute the content associated with the notification. This could
+         * mean silencing a conversation or currently playing media.
+         */
+        public static final int SEMANTIC_ACTION_MUTE = 6;
+
+        /**
+         * {@link SemanticAction}: Unmute the content associated with the notification. This could
+         * mean un-silencing a conversation or currently playing media.
+         */
+        public static final int SEMANTIC_ACTION_UNMUTE = 7;
+
+        /**
+         * {@link SemanticAction}: Mark content with a thumbs up.
+         */
+        public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
+
+        /**
+         * {@link SemanticAction}: Mark content with a thumbs down.
+         */
+        public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
+
+        static final String EXTRA_SHOWS_USER_INTERFACE =
+                "android.support.action.showsUserInterface";
+
         final Bundle mExtras;
         private final RemoteInput[] mRemoteInputs;
 
@@ -2760,6 +2870,9 @@
         private final RemoteInput[] mDataOnlyRemoteInputs;
 
         private boolean mAllowGeneratedReplies;
+        private boolean mShowsUserInterface = true;
+
+        private final @SemanticAction int mSemanticAction;
 
         /**
          * Small icon representing the action.
@@ -2776,12 +2889,13 @@
         public PendingIntent actionIntent;
 
         public Action(int icon, CharSequence title, PendingIntent intent) {
-            this(icon, title, intent, new Bundle(), null, null, true);
+            this(icon, title, intent, new Bundle(), null, null, true, SEMANTIC_ACTION_NONE, true);
         }
 
         Action(int icon, CharSequence title, PendingIntent intent, Bundle extras,
                 RemoteInput[] remoteInputs, RemoteInput[] dataOnlyRemoteInputs,
-                boolean allowGeneratedReplies) {
+                boolean allowGeneratedReplies, @SemanticAction int semanticAction,
+                boolean showsUserInterface) {
             this.icon = icon;
             this.title = NotificationCompat.Builder.limitCharSequenceLength(title);
             this.actionIntent = intent;
@@ -2789,6 +2903,8 @@
             this.mRemoteInputs = remoteInputs;
             this.mDataOnlyRemoteInputs = dataOnlyRemoteInputs;
             this.mAllowGeneratedReplies = allowGeneratedReplies;
+            this.mSemanticAction = semanticAction;
+            this.mShowsUserInterface = showsUserInterface;
         }
 
         public int getIcon() {
@@ -2828,6 +2944,17 @@
         }
 
         /**
+         * Returns the {@link SemanticAction} associated with this {@link Action}. A
+         * {@link SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
+         * (eg. reply, mark as read, delete, etc).
+         *
+         * @see SemanticAction
+         */
+        public @SemanticAction int getSemanticAction() {
+            return mSemanticAction;
+        }
+
+        /**
          * Get the list of inputs to be collected from the user that ONLY accept data when this
          * action is sent. These remote inputs are guaranteed to return true on a call to
          * {@link RemoteInput#isDataOnly}.
@@ -2842,6 +2969,14 @@
         }
 
         /**
+         * Return whether or not triggering this {@link Action}'s {@link PendingIntent} will open a
+         * user interface.
+         */
+        public boolean getShowsUserInterface() {
+            return mShowsUserInterface;
+        }
+
+        /**
          * Builder class for {@link Action} objects.
          */
         public static final class Builder {
@@ -2851,6 +2986,8 @@
             private boolean mAllowGeneratedReplies = true;
             private final Bundle mExtras;
             private ArrayList<RemoteInput> mRemoteInputs;
+            private @SemanticAction int mSemanticAction;
+            private boolean mShowsUserInterface = true;
 
             /**
              * Construct a new builder for {@link Action} object.
@@ -2859,7 +2996,7 @@
              * @param intent the {@link PendingIntent} to fire when users trigger this action
              */
             public Builder(int icon, CharSequence title, PendingIntent intent) {
-                this(icon, title, intent, new Bundle(), null, true);
+                this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, true);
             }
 
             /**
@@ -2869,11 +3006,13 @@
              */
             public Builder(Action action) {
                 this(action.icon, action.title, action.actionIntent, new Bundle(action.mExtras),
-                        action.getRemoteInputs(), action.getAllowGeneratedReplies());
+                        action.getRemoteInputs(), action.getAllowGeneratedReplies(),
+                        action.getSemanticAction(), action.mShowsUserInterface);
             }
 
             private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras,
-                    RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
+                    RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
+                    @SemanticAction int semanticAction, boolean showsUserInterface) {
                 mIcon = icon;
                 mTitle = NotificationCompat.Builder.limitCharSequenceLength(title);
                 mIntent = intent;
@@ -2881,6 +3020,8 @@
                 mRemoteInputs = remoteInputs == null ? null : new ArrayList<>(
                         Arrays.asList(remoteInputs));
                 mAllowGeneratedReplies = allowGeneratedReplies;
+                mSemanticAction = semanticAction;
+                mShowsUserInterface = showsUserInterface;
             }
 
             /**
@@ -2936,6 +3077,32 @@
             }
 
             /**
+             * Sets the {@link SemanticAction} for this {@link Action}. A {@link SemanticAction}
+             * denotes what an {@link Action}'s {@link PendingIntent} will do (eg. reply, mark
+             * as read, delete, etc).
+             * @param semanticAction a {@link SemanticAction} defined within {@link Action} with
+             * {@code SEMANTIC_ACTION_} prefixes
+             * @return this object for method chaining
+             */
+            public Builder setSemanticAction(@SemanticAction int semanticAction) {
+                mSemanticAction = semanticAction;
+                return this;
+            }
+
+            /**
+             * Set whether or not this {@link Action}'s {@link PendingIntent} will open a user
+             * interface.
+             * @param showsUserInterface {@code true} if this {@link Action}'s {@link PendingIntent}
+             * will open a user interface, otherwise {@code false}
+             * @return this object for method chaining
+             * The default value is {@code true}
+             */
+            public Builder setShowsUserInterface(boolean showsUserInterface) {
+                mShowsUserInterface = showsUserInterface;
+                return this;
+            }
+
+            /**
              * Apply an extender to this action builder. Extenders may be used to add
              * metadata or change options on this builder.
              */
@@ -2966,7 +3133,8 @@
                 RemoteInput[] textInputsArr = textInputs.isEmpty()
                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
-                        dataOnlyInputsArr, mAllowGeneratedReplies);
+                        dataOnlyInputsArr, mAllowGeneratedReplies, mSemanticAction,
+                        mShowsUserInterface);
             }
         }
 
@@ -3226,6 +3394,27 @@
                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
             }
         }
+
+        /**
+         * Provides meaning to an {@link Action} that hints at what the associated
+         * {@link PendingIntent} will do. For example, an {@link Action} with a
+         * {@link PendingIntent} that replies to a text message notification may have the
+         * {@link #SEMANTIC_ACTION_REPLY} {@link SemanticAction} set within it.
+         */
+        @IntDef({
+                SEMANTIC_ACTION_NONE,
+                SEMANTIC_ACTION_REPLY,
+                SEMANTIC_ACTION_MARK_AS_READ,
+                SEMANTIC_ACTION_MARK_AS_UNREAD,
+                SEMANTIC_ACTION_DELETE,
+                SEMANTIC_ACTION_ARCHIVE,
+                SEMANTIC_ACTION_MUTE,
+                SEMANTIC_ACTION_UNMUTE,
+                SEMANTIC_ACTION_THUMBS_UP,
+                SEMANTIC_ACTION_THUMBS_DOWN
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface SemanticAction {}
     }
 
 
@@ -4626,8 +4815,13 @@
             allowGeneratedReplies = action.getExtras().getBoolean(
                     NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES);
         }
+
+        final boolean showsUserInterface =
+                action.getExtras().getBoolean(Action.EXTRA_SHOWS_USER_INTERFACE, true);
+
         return new Action(action.icon, action.title, action.actionIntent,
-                action.getExtras(), remoteInputs, null, allowGeneratedReplies);
+                action.getExtras(), remoteInputs, null, allowGeneratedReplies,
+                Action.SEMANTIC_ACTION_NONE, showsUserInterface);
     }
 
     /**
diff --git a/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java b/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java
index db775a5..e5703a7 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java
@@ -248,6 +248,8 @@
             if (Build.VERSION.SDK_INT >= 24) {
                 actionBuilder.setAllowGeneratedReplies(action.getAllowGeneratedReplies());
             }
+            actionExtras.putBoolean(NotificationCompat.Action.EXTRA_SHOWS_USER_INTERFACE,
+                    action.getShowsUserInterface());
             actionBuilder.addExtras(actionExtras);
             mBuilder.addAction(actionBuilder.build());
         } else if (Build.VERSION.SDK_INT >= 16) {
diff --git a/compat/src/main/java/android/support/v4/app/NotificationCompatJellybean.java b/compat/src/main/java/android/support/v4/app/NotificationCompatJellybean.java
index 9cdd2e9..82f8941 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationCompatJellybean.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationCompatJellybean.java
@@ -129,7 +129,8 @@
             allowGeneratedReplies = extras.getBoolean(EXTRA_ALLOW_GENERATED_REPLIES);
         }
         return new NotificationCompat.Action(icon, title, actionIntent, extras, remoteInputs,
-                dataOnlyRemoteInputs, allowGeneratedReplies);
+                dataOnlyRemoteInputs, allowGeneratedReplies,
+                NotificationCompat.Action.SEMANTIC_ACTION_NONE, true);
     }
 
     public static Bundle writeActionAndGetExtras(
@@ -236,7 +237,9 @@
                 bundle.getBundle(KEY_EXTRAS),
                 fromBundleArray(getBundleArrayFromBundle(bundle, KEY_REMOTE_INPUTS)),
                 fromBundleArray(getBundleArrayFromBundle(bundle, KEY_DATA_ONLY_REMOTE_INPUTS)),
-                allowGeneratedReplies);
+                allowGeneratedReplies,
+                NotificationCompat.Action.SEMANTIC_ACTION_NONE,
+                true);
     }
 
     static Bundle getBundleForAction(NotificationCompat.Action action) {
diff --git a/compat/src/main/java/android/support/v4/content/res/FontResourcesParserCompat.java b/compat/src/main/java/android/support/v4/content/res/FontResourcesParserCompat.java
index 8ad07d3..f597e68 100644
--- a/compat/src/main/java/android/support/v4/content/res/FontResourcesParserCompat.java
+++ b/compat/src/main/java/android/support/v4/content/res/FontResourcesParserCompat.java
@@ -102,13 +102,17 @@
         private final @NonNull String mFileName;
         private int mWeight;
         private boolean mItalic;
+        private String mVariationSettings;
+        private int mTtcIndex;
         private int mResourceId;
 
         public FontFileResourceEntry(@NonNull String fileName, int weight, boolean italic,
-                int resourceId) {
+                @Nullable String variationSettings, int ttcIndex, int resourceId) {
             mFileName = fileName;
             mWeight = weight;
             mItalic = italic;
+            mVariationSettings = variationSettings;
+            mTtcIndex = ttcIndex;
             mResourceId = resourceId;
         }
 
@@ -124,6 +128,14 @@
             return mItalic;
         }
 
+        public @Nullable String getVariationSettings() {
+            return mVariationSettings;
+        }
+
+        public int getTtcIndex() {
+            return mTtcIndex;
+        }
+
         public int getResourceId() {
             return mResourceId;
         }
@@ -260,6 +272,15 @@
                 ? R.styleable.FontFamilyFont_fontStyle
                 : R.styleable.FontFamilyFont_android_fontStyle;
         boolean isItalic = ITALIC == array.getInt(styleAttr, 0);
+        final int ttcIndexAttr = array.hasValue(R.styleable.FontFamilyFont_ttcIndex)
+                ? R.styleable.FontFamilyFont_ttcIndex
+                : R.styleable.FontFamilyFont_android_ttcIndex;
+        final int variationSettingsAttr =
+                array.hasValue(R.styleable.FontFamilyFont_fontVariationSettings)
+                        ? R.styleable.FontFamilyFont_fontVariationSettings
+                        : R.styleable.FontFamilyFont_android_fontVariationSettings;
+        String variationSettings = array.getString(variationSettingsAttr);
+        int ttcIndex = array.getInt(ttcIndexAttr, 0);
         final int resourceAttr = array.hasValue(R.styleable.FontFamilyFont_font)
                 ? R.styleable.FontFamilyFont_font
                 : R.styleable.FontFamilyFont_android_font;
@@ -269,7 +290,8 @@
         while (parser.next() != XmlPullParser.END_TAG) {
             skip(parser);
         }
-        return new FontFileResourceEntry(filename, weight, isItalic, resourceId);
+        return new FontFileResourceEntry(filename, weight, isItalic, variationSettings, ttcIndex,
+                resourceId);
     }
 
     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompat.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompat.java
index 3c55df6..b763101 100644
--- a/compat/src/main/java/android/support/v4/graphics/TypefaceCompat.java
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompat.java
@@ -32,10 +32,10 @@
 import android.support.v4.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry;
 import android.support.v4.content.res.FontResourcesParserCompat.ProviderResourceEntry;
 import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.os.BuildCompat;
 import android.support.v4.provider.FontsContractCompat;
 import android.support.v4.provider.FontsContractCompat.FontInfo;
 import android.support.v4.util.LruCache;
-
 /**
  * Helper for accessing features in {@link Typeface}.
  * @hide
@@ -46,7 +46,9 @@
 
     private static final TypefaceCompatImpl sTypefaceCompatImpl;
     static {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+        if (BuildCompat.isAtLeastP()) {
+            sTypefaceCompatImpl = new TypefaceCompatApi28Impl();
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             sTypefaceCompatImpl = new TypefaceCompatApi26Impl();
         } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                 && TypefaceCompatApi24Impl.isUsable()) {
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi24Impl.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi24Impl.java
index 89a6ec4..a8c1988 100644
--- a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi24Impl.java
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi24Impl.java
@@ -159,8 +159,7 @@
             if (buffer == null) {
                 return null;
             }
-            // TODO: support ttc index.
-            if (!addFontWeightStyle(family, buffer, 0, e.getWeight(), e.isItalic())) {
+            if (!addFontWeightStyle(family, buffer, e.getTtcIndex(), e.getWeight(), e.isItalic())) {
                 return null;
             }
         }
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index 28ab3ed..955284e 100644
--- a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -60,76 +60,70 @@
             "createFromFamiliesWithDefault";
     private static final String FREEZE_METHOD = "freeze";
     private static final String ABORT_CREATION_METHOD = "abortCreation";
-    private static final Class sFontFamily;
-    private static final Constructor sFontFamilyCtor;
-    private static final Method sAddFontFromAssetManager;
-    private static final Method sAddFontFromBuffer;
-    private static final Method sFreeze;
-    private static final Method sAbortCreation;
-    private static final Method sCreateFromFamiliesWithDefault;
     private static final int RESOLVE_BY_FONT_TABLE = -1;
+    private static final String DEFAULT_FAMILY = "sans-serif";
 
-    static {
-        Class fontFamilyClass;
+    protected final Class mFontFamily;
+    protected final Constructor mFontFamilyCtor;
+    protected final Method mAddFontFromAssetManager;
+    protected final Method mAddFontFromBuffer;
+    protected final Method mFreeze;
+    protected final Method mAbortCreation;
+    protected final Method mCreateFromFamiliesWithDefault;
+
+    public TypefaceCompatApi26Impl() {
+        Class fontFamily;
         Constructor fontFamilyCtor;
-        Method addFontMethod;
-        Method addFromBufferMethod;
-        Method freezeMethod;
-        Method abortCreationMethod;
-        Method createFromFamiliesWithDefaultMethod;
+        Method addFontFromAssetManager;
+        Method addFontFromBuffer;
+        Method freeze;
+        Method abortCreation;
+        Method createFromFamiliesWithDefault;
         try {
-            fontFamilyClass = Class.forName(FONT_FAMILY_CLASS);
-            fontFamilyCtor = fontFamilyClass.getConstructor();
-            addFontMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
-                    AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
-                    Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
-            addFromBufferMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
-                    ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
-                    Integer.TYPE);
-            freezeMethod = fontFamilyClass.getMethod(FREEZE_METHOD);
-            abortCreationMethod = fontFamilyClass.getMethod(ABORT_CREATION_METHOD);
-            Object familyArray = Array.newInstance(fontFamilyClass, 1);
-            createFromFamiliesWithDefaultMethod =
-                    Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
-                            familyArray.getClass(), Integer.TYPE, Integer.TYPE);
-            createFromFamiliesWithDefaultMethod.setAccessible(true);
+            fontFamily = obtainFontFamily();
+            fontFamilyCtor = obtainFontFamilyCtor(fontFamily);
+            addFontFromAssetManager = obtainAddFontFromAssetManagerMethod(fontFamily);
+            addFontFromBuffer = obtainAddFontFromBufferMethod(fontFamily);
+            freeze = obtainFreezeMethod(fontFamily);
+            abortCreation = obtainAbortCreationMethod(fontFamily);
+            createFromFamiliesWithDefault = obtainCreateFromFamiliesWithDefaultMethod(fontFamily);
         } catch (ClassNotFoundException | NoSuchMethodException e) {
             Log.e(TAG, "Unable to collect necessary methods for class " + e.getClass().getName(),
                     e);
-            fontFamilyClass = null;
+            fontFamily = null;
             fontFamilyCtor = null;
-            addFontMethod = null;
-            addFromBufferMethod = null;
-            freezeMethod = null;
-            abortCreationMethod = null;
-            createFromFamiliesWithDefaultMethod = null;
+            addFontFromAssetManager = null;
+            addFontFromBuffer = null;
+            freeze = null;
+            abortCreation = null;
+            createFromFamiliesWithDefault = null;
         }
-        sFontFamilyCtor = fontFamilyCtor;
-        sFontFamily = fontFamilyClass;
-        sAddFontFromAssetManager = addFontMethod;
-        sAddFontFromBuffer = addFromBufferMethod;
-        sFreeze = freezeMethod;
-        sAbortCreation = abortCreationMethod;
-        sCreateFromFamiliesWithDefault = createFromFamiliesWithDefaultMethod;
+        mFontFamily = fontFamily;
+        mFontFamilyCtor = fontFamilyCtor;
+        mAddFontFromAssetManager = addFontFromAssetManager;
+        mAddFontFromBuffer = addFontFromBuffer;
+        mFreeze = freeze;
+        mAbortCreation = abortCreation;
+        mCreateFromFamiliesWithDefault = createFromFamiliesWithDefault;
     }
 
     /**
-     * Returns true if API26 implementation is usable.
+     * Returns true if all the necessary methods were found.
      */
-    private static boolean isFontFamilyPrivateAPIAvailable() {
-        if (sAddFontFromAssetManager == null) {
+    private boolean isFontFamilyPrivateAPIAvailable() {
+        if (mAddFontFromAssetManager == null) {
             Log.w(TAG, "Unable to collect necessary private methods. "
                     + "Fallback to legacy implementation.");
         }
-        return sAddFontFromAssetManager != null;
+        return mAddFontFromAssetManager != null;
     }
 
     /**
      * Create a new FontFamily instance
      */
-    private static Object newFamily() {
+    private Object newFamily() {
         try {
-            return sFontFamilyCtor.newInstance();
+            return mFontFamilyCtor.newInstance();
         } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
             throw new RuntimeException(e);
         }
@@ -139,12 +133,12 @@
      * Call FontFamily#addFontFromAssetManager(AssetManager mgr, String path, int cookie,
      *      boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes)
      */
-    private static boolean addFontFromAssetManager(Context context, Object family, String fileName,
-            int ttcIndex, int weight, int style) {
+    private boolean addFontFromAssetManager(Context context, Object family, String fileName,
+            int ttcIndex, int weight, int style, @Nullable FontVariationAxis[] axes) {
         try {
-            final Boolean result = (Boolean) sAddFontFromAssetManager.invoke(family,
+            final Boolean result = (Boolean) mAddFontFromAssetManager.invoke(family,
                     context.getAssets(), fileName, 0 /* cookie */, false /* isAsset */, ttcIndex,
-                    weight, style, null /* axes */);
+                    weight, style, axes);
             return result.booleanValue();
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
@@ -155,10 +149,10 @@
      * Call FontFamily#addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
      *      int weight, int italic)
      */
-    private static boolean addFontFromBuffer(Object family, ByteBuffer buffer,
+    private boolean addFontFromBuffer(Object family, ByteBuffer buffer,
             int ttcIndex, int weight, int style) {
         try {
-            final Boolean result = (Boolean) sAddFontFromBuffer.invoke(family,
+            final Boolean result = (Boolean) mAddFontFromBuffer.invoke(family,
                     buffer, ttcIndex, null /* axes */, weight, style);
             return result.booleanValue();
         } catch (IllegalAccessException | InvocationTargetException e) {
@@ -167,14 +161,14 @@
     }
 
     /**
-     * Call static method Typeface#createFromFamiliesWithDefault(
+     * Call method Typeface#createFromFamiliesWithDefault(
      *      FontFamily[] families, int weight, int italic)
      */
-    private static Typeface createFromFamiliesWithDefault(Object family) {
+    protected Typeface createFromFamiliesWithDefault(Object family) {
         try {
-            Object familyArray = Array.newInstance(sFontFamily, 1);
+            Object familyArray = Array.newInstance(mFontFamily, 1);
             Array.set(familyArray, 0, family);
-            return (Typeface) sCreateFromFamiliesWithDefault.invoke(null /* static method */,
+            return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
                     familyArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
@@ -184,9 +178,9 @@
     /**
      * Call FontFamily#freeze()
      */
-    private static boolean freeze(Object family) {
+    private boolean freeze(Object family) {
         try {
-            Boolean result = (Boolean) sFreeze.invoke(family);
+            Boolean result = (Boolean) mFreeze.invoke(family);
             return result.booleanValue();
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
@@ -196,9 +190,9 @@
     /**
      * Call FontFamily#abortCreation()
      */
-    private static void abortCreation(Object family) {
+    private void abortCreation(Object family) {
         try {
-            sAbortCreation.invoke(family);
+            mAbortCreation.invoke(family);
         } catch (IllegalAccessException | InvocationTargetException e) {
             throw new RuntimeException(e);
         }
@@ -213,9 +207,9 @@
         }
         Object fontFamily = newFamily();
         for (final FontFileResourceEntry fontFile : entry.getEntries()) {
-            // TODO: Add ttc and variation font support. (b/37853920)
             if (!addFontFromAssetManager(context, fontFamily, fontFile.getFileName(),
-                    0 /* ttcIndex */, fontFile.getWeight(), fontFile.isItalic() ? 1 : 0)) {
+                    fontFile.getTtcIndex(), fontFile.getWeight(), fontFile.isItalic() ? 1 : 0,
+                    FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
                 abortCreation(fontFamily);
                 return null;
             }
@@ -292,7 +286,7 @@
         Object fontFamily = newFamily();
         if (!addFontFromAssetManager(context, fontFamily, path,
                 0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */,
-                RESOLVE_BY_FONT_TABLE /* italic */)) {
+                RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
             abortCreation(fontFamily);
             return null;
         }
@@ -301,4 +295,47 @@
         }
         return createFromFamiliesWithDefault(fontFamily);
     }
+
+    // The following getters retrieve by reflection the Typeface methods, belonging to the
+    // framework code, which will be invoked. Since the definitions of these methods can change
+    // across different API versions, inheriting classes should override these getters in order to
+    // reflect the method definitions in the API versions they represent.
+    //===========================================================================================
+    protected Class obtainFontFamily() throws ClassNotFoundException {
+        return Class.forName(FONT_FAMILY_CLASS);
+    }
+
+    protected Constructor obtainFontFamilyCtor(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getConstructor();
+    }
+
+    protected Method obtainAddFontFromAssetManagerMethod(Class fontFamily)
+            throws NoSuchMethodException {
+        return fontFamily.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
+                AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
+                Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
+    }
+
+    protected Method obtainAddFontFromBufferMethod(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
+                ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
+                Integer.TYPE);
+    }
+
+    protected Method obtainFreezeMethod(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getMethod(FREEZE_METHOD);
+    }
+
+    protected Method obtainAbortCreationMethod(Class fontFamily) throws NoSuchMethodException {
+        return fontFamily.getMethod(ABORT_CREATION_METHOD);
+    }
+
+    protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+            throws NoSuchMethodException {
+        Object familyArray = Array.newInstance(fontFamily, 1);
+        Method m =  Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+                familyArray.getClass(), Integer.TYPE, Integer.TYPE);
+        m.setAccessible(true);
+        return m;
+    }
 }
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi28Impl.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi28Impl.java
new file mode 100644
index 0000000..baa2ce6
--- /dev/null
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi28Impl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+package android.support.v4.graphics;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.graphics.Typeface;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Implementation of the Typeface compat methods for API 28 and above.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+@RequiresApi(28)
+public class TypefaceCompatApi28Impl extends TypefaceCompatApi26Impl {
+    private static final String TAG = "TypefaceCompatApi28Impl";
+
+    private static final String CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD =
+            "createFromFamiliesWithDefault";
+    private static final int RESOLVE_BY_FONT_TABLE = -1;
+    private static final String DEFAULT_FAMILY = "sans-serif";
+
+    /**
+     * Call method Typeface#createFromFamiliesWithDefault(
+     *      FontFamily[] families, String fallbackName, int weight, int italic)
+     */
+    @Override
+    protected Typeface createFromFamiliesWithDefault(Object family) {
+        try {
+            Object familyArray = Array.newInstance(mFontFamily, 1);
+            Array.set(familyArray, 0, family);
+            return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
+                    familyArray, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+            throws NoSuchMethodException {
+        Object familyArray = Array.newInstance(fontFamily, 1);
+        Method m =  Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+                familyArray.getClass(), String.class, Integer.TYPE, Integer.TYPE);
+        m.setAccessible(true);
+        return m;
+    }
+}
diff --git a/compat/src/main/java/android/support/v4/graphics/drawable/IconCompat.java b/compat/src/main/java/android/support/v4/graphics/drawable/IconCompat.java
index 359c96b..dc226c1 100644
--- a/compat/src/main/java/android/support/v4/graphics/drawable/IconCompat.java
+++ b/compat/src/main/java/android/support/v4/graphics/drawable/IconCompat.java
@@ -220,6 +220,7 @@
      * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
+    @SuppressWarnings("deprecation")
     public void addToShortcutIntent(@NonNull Intent outIntent, @Nullable Drawable badge) {
         Bitmap icon;
         switch (mType) {
diff --git a/compat/src/main/java/android/support/v4/util/LongSparseArray.java b/compat/src/main/java/android/support/v4/util/LongSparseArray.java
index 25b6bb9..febb5d5 100644
--- a/compat/src/main/java/android/support/v4/util/LongSparseArray.java
+++ b/compat/src/main/java/android/support/v4/util/LongSparseArray.java
@@ -235,6 +235,14 @@
     }
 
     /**
+     * Return true if size() is 0.
+     * @return true if size() is 0.
+     */
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+    /**
      * Given an index in the range <code>0...size()-1</code>, returns
      * the key from the <code>index</code>th key-value mapping that this
      * LongSparseArray stores.
diff --git a/compat/src/main/java/android/support/v4/util/SparseArrayCompat.java b/compat/src/main/java/android/support/v4/util/SparseArrayCompat.java
index aedc4ad..5238cf0 100644
--- a/compat/src/main/java/android/support/v4/util/SparseArrayCompat.java
+++ b/compat/src/main/java/android/support/v4/util/SparseArrayCompat.java
@@ -228,6 +228,14 @@
     }
 
     /**
+     * Return true if size() is 0.
+     * @return true if size() is 0.
+     */
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+    /**
      * Given an index in the range <code>0...size()-1</code>, returns
      * the key from the <code>index</code>th key-value mapping that this
      * SparseArray stores.
diff --git a/compat/src/main/java/android/support/v4/view/ViewConfigurationCompat.java b/compat/src/main/java/android/support/v4/view/ViewConfigurationCompat.java
index 60d37a9..a12387b 100644
--- a/compat/src/main/java/android/support/v4/view/ViewConfigurationCompat.java
+++ b/compat/src/main/java/android/support/v4/view/ViewConfigurationCompat.java
@@ -117,5 +117,17 @@
         return 0;
     }
 
+    /**
+     * @param config Used to get the hover slop directly from the {@link ViewConfiguration}.
+     *
+     * @return The hover slop value.
+     */
+    public static int getScaledHoverSlop(ViewConfiguration config) {
+        if (android.os.Build.VERSION.SDK_INT >= 28) {
+            return config.getScaledHoverSlop();
+        }
+        return config.getScaledTouchSlop() / 2;
+    }
+
     private ViewConfigurationCompat() {}
 }
diff --git a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
index 41da709..cca5324 100644
--- a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
+++ b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
@@ -200,6 +200,13 @@
     }
 
     @Test
+    public void testNotificationActionBuilder_defaultShowsUserInterfaceTrue() {
+        NotificationCompat.Action action = newActionBuilder().build();
+
+        assertTrue(action.getShowsUserInterface());
+    }
+
+    @Test
     public void testNotificationAction_defaultAllowGeneratedRepliesTrue() throws Throwable {
         NotificationCompat.Action a = new NotificationCompat.Action(0, null, null);
 
@@ -207,6 +214,13 @@
     }
 
     @Test
+    public void testNotificationAction_defaultShowsUserInterfaceTrue() {
+        NotificationCompat.Action action = new NotificationCompat.Action(0, null, null);
+
+        assertTrue(action.getShowsUserInterface());
+    }
+
+    @Test
     public void testNotificationActionBuilder_setAllowGeneratedRepliesFalse() throws Throwable {
         NotificationCompat.Action a = newActionBuilder()
                 .setAllowGeneratedReplies(false).build();
@@ -214,6 +228,28 @@
         assertFalse(a.getAllowGeneratedReplies());
     }
 
+    @Test
+    public void testNotificationAction_setShowsUserInterfaceFalse() {
+        NotificationCompat.Action action = newActionBuilder()
+                .setShowsUserInterface(false).build();
+
+        assertFalse(action.getShowsUserInterface());
+    }
+
+    @SdkSuppress(minSdkVersion = 20)
+    @Test
+    public void testGetActionCompatFromAction_showsUserInterface() {
+        NotificationCompat.Action action = newActionBuilder()
+                .setShowsUserInterface(false).build();
+        Notification notification = newNotificationBuilder().addAction(action).build();
+        NotificationCompat.Action result =
+                NotificationCompat.getActionCompatFromAction(notification.actions[0]);
+
+        assertFalse(result.getExtras().getBoolean(
+                NotificationCompat.Action.EXTRA_SHOWS_USER_INTERFACE, true));
+        assertFalse(result.getShowsUserInterface());
+    }
+
     @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testNotificationWearableExtenderAction_setAllowGeneratedRepliesTrue()
@@ -488,6 +524,103 @@
         assertEquals(100, n.ledOffMS);
     }
 
+    @Test
+    public void messagingStyle_isGroupConversation() {
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
+        NotificationCompat.MessagingStyle messagingStyle =
+                new NotificationCompat.MessagingStyle("self name")
+                        .setGroupConversation(true)
+                        .setConversationTitle("test conversation title");
+        new NotificationCompat.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+    }
+
+    @Test
+    public void messagingStyle_isGroupConversation_noConversationTitle() {
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
+        NotificationCompat.MessagingStyle messagingStyle =
+                new NotificationCompat.MessagingStyle("self name")
+                        .setGroupConversation(true)
+                        .setConversationTitle(null);
+        new NotificationCompat.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+    }
+
+    @Test
+    public void messagingStyle_isGroupConversation_withConversationTitle_legacy() {
+        // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
+        NotificationCompat.MessagingStyle messagingStyle =
+                new NotificationCompat.MessagingStyle("self name")
+                        .setGroupConversation(false)
+                        .setConversationTitle("test conversation title");
+        new NotificationCompat.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+    }
+
+    @Test
+    public void messagingStyle_isGroupConversation_withoutConversationTitle_legacy() {
+        // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
+        NotificationCompat.MessagingStyle messagingStyle =
+                new NotificationCompat.MessagingStyle("self name")
+                        .setGroupConversation(true)
+                        .setConversationTitle(null);
+        new NotificationCompat.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertFalse(messagingStyle.isGroupConversation());
+    }
+
+    @Test
+    public void testMessagingStyle_extras() {
+        NotificationCompat.MessagingStyle messagingStyle =
+                new NotificationCompat.MessagingStyle("test name")
+                        .setGroupConversation(true);
+        Bundle bundle = new Bundle();
+        messagingStyle.addCompatExtras(bundle);
+
+        NotificationCompat.MessagingStyle resultMessagingStyle =
+                new NotificationCompat.MessagingStyle("test name");
+        resultMessagingStyle.restoreFromCompatExtras(bundle);
+
+        assertTrue(resultMessagingStyle.isGroupConversation());
+    }
+
+    @Test
+    public void action_builder_hasDefault() {
+        NotificationCompat.Action action =
+                new NotificationCompat.Action.Builder(0, "Test Title", null).build();
+        assertEquals(NotificationCompat.Action.SEMANTIC_ACTION_NONE, action.getSemanticAction());
+    }
+
+    @Test
+    public void action_builder_setSemanticAction() {
+        NotificationCompat.Action action =
+                new NotificationCompat.Action.Builder(0, "Test Title", null)
+                        .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
+                        .build();
+        assertEquals(NotificationCompat.Action.SEMANTIC_ACTION_REPLY, action.getSemanticAction());
+    }
+
     private static RemoteInput newDataOnlyRemoteInput() {
         return new RemoteInput.Builder(DATA_RESULT_KEY)
             .setAllowFreeFormInput(false)
diff --git a/compat/tests/java/android/support/v4/content/res/FontResourcesParserCompatTest.java b/compat/tests/java/android/support/v4/content/res/FontResourcesParserCompatTest.java
index 6120eed..f503d99 100644
--- a/compat/tests/java/android/support/v4/content/res/FontResourcesParserCompatTest.java
+++ b/compat/tests/java/android/support/v4/content/res/FontResourcesParserCompatTest.java
@@ -63,7 +63,7 @@
     @Test
     public void testParse() throws XmlPullParserException, IOException {
         @SuppressLint("ResourceType")
-        XmlResourceParser parser = mResources.getXml(R.font.samplexmlfont);
+        XmlResourceParser parser = mResources.getXml(R.font.samplexmlfontforparsing);
 
         FamilyResourceEntry result = FontResourcesParserCompat.parse(parser, mResources);
 
@@ -74,18 +74,26 @@
         FontFileResourceEntry font1 = fileEntries[0];
         assertEquals(400, font1.getWeight());
         assertEquals(false, font1.isItalic());
+        assertEquals("'wdth' 0.8", font1.getVariationSettings());
+        assertEquals(0, font1.getTtcIndex());
         assertEquals(R.font.samplefont, font1.getResourceId());
         FontFileResourceEntry font2 = fileEntries[1];
         assertEquals(400, font2.getWeight());
         assertEquals(true, font2.isItalic());
+        assertEquals("'contrast' 0.5", font2.getVariationSettings());
+        assertEquals(1, font2.getTtcIndex());
         assertEquals(R.font.samplefont2, font2.getResourceId());
         FontFileResourceEntry font3 = fileEntries[2];
         assertEquals(700, font3.getWeight());
         assertEquals(false, font3.isItalic());
+        assertEquals("'wdth' 500.0, 'wght' 300.0", font3.getVariationSettings());
+        assertEquals(2, font3.getTtcIndex());
         assertEquals(R.font.samplefont3, font3.getResourceId());
         FontFileResourceEntry font4 = fileEntries[3];
         assertEquals(700, font4.getWeight());
         assertEquals(true, font4.isItalic());
+        assertEquals(null, font4.getVariationSettings());
+        assertEquals(0, font4.getTtcIndex());
         assertEquals(R.font.samplefont4, font4.getResourceId());
     }
 
@@ -98,7 +106,7 @@
         }
 
         @SuppressLint("ResourceType")
-        XmlResourceParser parser = mResources.getXml(R.font.samplexmlfont2);
+        XmlResourceParser parser = mResources.getXml(R.font.samplexmlfontforparsing2);
 
         FamilyResourceEntry result = FontResourcesParserCompat.parse(parser, mResources);
 
@@ -109,18 +117,26 @@
         FontFileResourceEntry font1 = fileEntries[0];
         assertEquals(400, font1.getWeight());
         assertEquals(false, font1.isItalic());
+        assertEquals("'wdth' 0.8", font1.getVariationSettings());
+        assertEquals(0, font1.getTtcIndex());
         assertEquals(R.font.samplefont, font1.getResourceId());
         FontFileResourceEntry font2 = fileEntries[1];
         assertEquals(400, font2.getWeight());
         assertEquals(true, font2.isItalic());
+        assertEquals("'contrast' 0.5", font2.getVariationSettings());
+        assertEquals(1, font2.getTtcIndex());
         assertEquals(R.font.samplefont2, font2.getResourceId());
         FontFileResourceEntry font3 = fileEntries[2];
         assertEquals(700, font3.getWeight());
         assertEquals(false, font3.isItalic());
+        assertEquals("'wdth' 500.0, 'wght' 300.0", font3.getVariationSettings());
+        assertEquals(2, font3.getTtcIndex());
         assertEquals(R.font.samplefont3, font3.getResourceId());
         FontFileResourceEntry font4 = fileEntries[3];
         assertEquals(700, font4.getWeight());
         assertEquals(true, font4.isItalic());
+        assertEquals(null, font4.getVariationSettings());
+        assertEquals(0, font4.getTtcIndex());
         assertEquals(R.font.samplefont4, font4.getResourceId());
     }
 
diff --git a/compat/tests/java/android/support/v4/graphics/TypefaceCompatTest.java b/compat/tests/java/android/support/v4/graphics/TypefaceCompatTest.java
index dff4c33..9c27966 100644
--- a/compat/tests/java/android/support/v4/graphics/TypefaceCompatTest.java
+++ b/compat/tests/java/android/support/v4/graphics/TypefaceCompatTest.java
@@ -31,6 +31,7 @@
 import android.content.res.Resources;
 import android.graphics.Paint;
 import android.graphics.Typeface;
+import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.compat.test.R;
 import android.support.test.InstrumentationRegistry;
@@ -356,6 +357,81 @@
         assertEquals(R.font.large_d, getSelectedFontResourceId(typeface));
     }
 
+    private Typeface getLargerTypeface(String text, Typeface typeface1, Typeface typeface2) {
+        Paint p1 = new Paint();
+        p1.setTypeface(typeface1);
+        float width1 = p1.measureText(text);
+        Paint p2 = new Paint();
+        p2.setTypeface(typeface2);
+        float width2 = p2.measureText(text);
+
+        if (width1 > width2) {
+            return typeface1;
+        } else if (width1 < width2) {
+            return typeface2;
+        } else {
+            assertTrue(false);
+            return null;
+        }
+    }
+
+    @Test
+    public void testCreateFromResourcesFamilyXml_resourceTtcFont() throws Exception {
+        // Here we test that building typefaces by indexing in font collections works correctly.
+        // We want to ensure that the built typefaces correspond to the fonts with the right index.
+        // sample_font_collection.ttc contains two fonts (with indices 0 and 1). The first one has
+        // glyph "a" of 3em width, and all the other glyphs 1em. The second one has glyph "b" of
+        // 3em width, and all the other glyphs 1em. Hence, we can compare the width of these
+        // glyphs to assert that ttc indexing works.
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            // Creating typefaces with ttc index was only supported in the API starting with N.
+            return;
+        }
+        final FamilyResourceEntry entry1 = FontResourcesParserCompat.parse(
+                mResources.getXml(R.font.ttctestfont1), mResources);
+        Typeface typeface1 = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry1,
+                mResources, R.font.ttctestfont1, Typeface.NORMAL, null /* callback */,
+                null /*handler */, false /* isXmlRequest */);
+        assertNotNull(typeface1);
+        final FamilyResourceEntry entry2 = FontResourcesParserCompat.parse(
+                mResources.getXml(R.font.ttctestfont2), mResources);
+        Typeface typeface2 = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry2,
+                mResources, R.font.ttctestfont2, Typeface.NORMAL, null /* callback */,
+                null /*handler */, false /* isXmlRequest */);
+        assertNotNull(typeface2);
+
+        assertEquals(getLargerTypeface("a", typeface1, typeface2), typeface1);
+        assertEquals(getLargerTypeface("b", typeface1, typeface2), typeface2);
+    }
+
+    @Test
+    public void testCreateFromResourcesFamilyXml_resourceFontWithVariationSettings()
+            throws Exception {
+        // Here we test that specifying variation settings for fonts in XMLs works correctly.
+        // We build typefaces from two families containing one font each, using the same font
+        // resource, but having different values for the 'wdth' tag. Then we measure the painted
+        // text to ensure that the tag affects the text width. The font resource used supports
+        // the 'wdth' axis for the dash (-) character.
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+            // Variation settings are only supported on O and newer.
+            return;
+        }
+        final FamilyResourceEntry entry1 = FontResourcesParserCompat.parse(
+                mResources.getXml(R.font.variationsettingstestfont1), mResources);
+        Typeface typeface1 = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry1,
+                mResources, R.font.variationsettingstestfont1, Typeface.NORMAL, null /* callback */,
+                null /*handler */, false /* isXmlRequest */);
+        assertNotNull(typeface1);
+        final FamilyResourceEntry entry2 = FontResourcesParserCompat.parse(
+                mResources.getXml(R.font.variationsettingstestfont2), mResources);
+        Typeface typeface2 = TypefaceCompat.createFromResourcesFamilyXml(mContext, entry2,
+                mResources, R.font.variationsettingstestfont2, Typeface.NORMAL, null /* callback */,
+                null /*handler */, false /* isXmlRequest */);
+        assertNotNull(typeface2);
+
+        assertEquals(getLargerTypeface("-", typeface1, typeface2), typeface2);
+    }
+
     @Test
     public void testCreateFromResourcesFontFile() {
         Typeface typeface = TypefaceCompat.createFromResourcesFontFile(mContext, mResources,
diff --git a/compat/tests/java/android/support/v4/util/LongSparseArrayTest.java b/compat/tests/java/android/support/v4/util/LongSparseArrayTest.java
new file mode 100644
index 0000000..663ea48
--- /dev/null
+++ b/compat/tests/java/android/support/v4/util/LongSparseArrayTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.support.v4.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LongSparseArrayTest {
+    @Test
+    public void isEmpty() throws Exception {
+        LongSparseArray<String> LongSparseArray = new LongSparseArray<>();
+        assertTrue(LongSparseArray.isEmpty()); // Newly created LongSparseArray should be empty
+
+        // Adding elements should change state from empty to not empty.
+        for (long i = 0L; i < 5L; i++) {
+            LongSparseArray.put(i, Long.toString(i));
+            assertFalse(LongSparseArray.isEmpty());
+        }
+        LongSparseArray.clear();
+        assertTrue(LongSparseArray.isEmpty()); // A cleared LongSparseArray should be empty.
+
+
+        long key1 = 1L, key2 = 2L;
+        String value1 = "some value", value2 = "some other value";
+        LongSparseArray.append(key1, value1);
+        assertFalse(LongSparseArray.isEmpty()); // has 1 element.
+        LongSparseArray.append(key2, value2);
+        assertFalse(LongSparseArray.isEmpty());  // has 2 elements.
+        assertFalse(LongSparseArray.isEmpty());  // consecutive calls should be OK.
+
+        LongSparseArray.remove(key1);
+        assertFalse(LongSparseArray.isEmpty()); // has 1 element.
+        LongSparseArray.remove(key2);
+        assertTrue(LongSparseArray.isEmpty());
+    }
+
+}
diff --git a/compat/tests/java/android/support/v4/util/SparseArrayCompatTest.java b/compat/tests/java/android/support/v4/util/SparseArrayCompatTest.java
new file mode 100644
index 0000000..122c89b
--- /dev/null
+++ b/compat/tests/java/android/support/v4/util/SparseArrayCompatTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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 android.support.v4.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SparseArrayCompatTest {
+    @Test
+    public void isEmpty() throws Exception {
+        SparseArrayCompat<String> sparseArrayCompat = new SparseArrayCompat<>();
+        assertTrue(sparseArrayCompat.isEmpty()); // Newly created SparseArrayCompat should be empty
+
+        // Adding elements should change state from empty to not empty.
+        for (int i = 0; i < 5; i++) {
+            sparseArrayCompat.put(i, Integer.toString(i));
+            assertFalse(sparseArrayCompat.isEmpty());
+        }
+        sparseArrayCompat.clear();
+        assertTrue(sparseArrayCompat.isEmpty()); // A cleared SparseArrayCompat should be empty.
+
+
+        int key1 = 1, key2 = 2;
+        String value1 = "some value", value2 = "some other value";
+        sparseArrayCompat.append(key1, value1);
+        assertFalse(sparseArrayCompat.isEmpty()); // has 1 element.
+        sparseArrayCompat.append(key2, value2);
+        assertFalse(sparseArrayCompat.isEmpty());  // has 2 elements.
+        assertFalse(sparseArrayCompat.isEmpty());  // consecutive calls should be OK.
+
+        sparseArrayCompat.remove(key1);
+        assertFalse(sparseArrayCompat.isEmpty()); // has 1 element.
+        sparseArrayCompat.remove(key2);
+        assertTrue(sparseArrayCompat.isEmpty());
+    }
+}
diff --git a/compat/tests/res/font/sample_font_collection.ttc b/compat/tests/res/font/sample_font_collection.ttc
new file mode 100644
index 0000000..9252f3d
--- /dev/null
+++ b/compat/tests/res/font/sample_font_collection.ttc
Binary files differ
diff --git a/compat/tests/res/font/samplexmlfontforparsing.xml b/compat/tests/res/font/samplexmlfontforparsing.xml
new file mode 100644
index 0000000..a96385c
--- /dev/null
+++ b/compat/tests/res/font/samplexmlfontforparsing.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:app="http://schemas.android.com/apk/res-auto">
+    <font app:fontStyle="normal" app:fontWeight="400" app:fontVariationSettings="'wdth' 0.8"
+          app:font="@font/samplefont" app:ttcIndex="0" />
+    <font app:fontStyle="italic" app:fontWeight="400" app:fontVariationSettings="'contrast' 0.5"
+          app:font="@font/samplefont2" app:ttcIndex="1" />
+    <font app:fontStyle="normal" app:fontWeight="700" app:fontVariationSettings="'wdth' 500.0, 'wght' 300.0"
+          app:font="@font/samplefont3" app:ttcIndex="2" />
+    <font app:fontStyle="italic" app:fontWeight="700" app:font="@font/samplefont4" />
+</font-family>
diff --git a/compat/tests/res/font/samplexmlfontforparsing2.xml b/compat/tests/res/font/samplexmlfontforparsing2.xml
new file mode 100644
index 0000000..eb310ba
--- /dev/null
+++ b/compat/tests/res/font/samplexmlfontforparsing2.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  -->
+
+<font-family xmlns:android="http://schemas.android.com/apk/res/android" >
+    <font android:fontStyle="normal" android:fontWeight="400" android:fontVariationSettings="'wdth' 0.8"
+          android:font="@font/samplefont" android:ttcIndex="0" />
+    <font android:fontStyle="italic" android:fontWeight="400" android:fontVariationSettings="'contrast' 0.5"
+          android:font="@font/samplefont2" android:ttcIndex="1" />
+    <font android:fontStyle="normal" android:fontWeight="700" android:fontVariationSettings="'wdth' 500.0, 'wght' 300.0"
+          android:font="@font/samplefont3" android:ttcIndex="2" />
+    <font android:fontStyle="italic" android:fontWeight="700" android:font="@font/samplefont4" />
+</font-family>
diff --git a/compat/tests/res/font/ttctestfont1.xml b/compat/tests/res/font/ttctestfont1.xml
new file mode 100644
index 0000000..a2b75e3
--- /dev/null
+++ b/compat/tests/res/font/ttctestfont1.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
+    <font app:font="@font/sample_font_collection" app:ttcIndex="0" />
+</font-family>
diff --git a/compat/tests/res/font/ttctestfont2.xml b/compat/tests/res/font/ttctestfont2.xml
new file mode 100644
index 0000000..e64ed6a
--- /dev/null
+++ b/compat/tests/res/font/ttctestfont2.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
+    <font app:font="@font/sample_font_collection" app:ttcIndex="1" />
+</font-family>
diff --git a/compat/tests/res/font/variable_width_dash_font.ttf b/compat/tests/res/font/variable_width_dash_font.ttf
new file mode 100644
index 0000000..f7a256a
--- /dev/null
+++ b/compat/tests/res/font/variable_width_dash_font.ttf
Binary files differ
diff --git a/compat/tests/res/font/variationsettingstestfont1.xml b/compat/tests/res/font/variationsettingstestfont1.xml
new file mode 100644
index 0000000..39052a6
--- /dev/null
+++ b/compat/tests/res/font/variationsettingstestfont1.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
+    <font app:font="@font/variable_width_dash_font" app:fontVariationSettings="'wdth' 100.0" />
+</font-family>
diff --git a/compat/tests/res/font/variationsettingstestfont2.xml b/compat/tests/res/font/variationsettingstestfont2.xml
new file mode 100644
index 0000000..90382d0
--- /dev/null
+++ b/compat/tests/res/font/variationsettingstestfont2.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
+    <font app:font="@font/variable_width_dash_font" app:fontVariationSettings="'wdth' 500.0" />
+</font-family>
diff --git a/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java b/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
index 8ec3eee..78555aa 100644
--- a/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
+++ b/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
@@ -34,6 +34,9 @@
  * for you; you must request the {@link android.Manifest.permission#WAKE_LOCK}
  * permission to use it.</p>
  *
+ * <p>Wakelocks held by this class are reported to tools as
+ * {@code "androidx.core:wake:<component-name>"}.</p>
+ *
  * <h3>Example</h3>
  *
  * <p>A {@link WakefulBroadcastReceiver} uses the method
@@ -103,7 +106,7 @@
 
             PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
             PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                    "wake:" + comp.flattenToShortString());
+                    "androidx.core:wake:" + comp.flattenToShortString());
             wl.setReferenceCounted(false);
             wl.acquire(60 * 1000);
             sActiveWakeLocks.put(id, wl);
diff --git a/design/api/27.0.0-SNAPSHOT.ignore b/design/api/27.0.0-SNAPSHOT.ignore
new file mode 100644
index 0000000..533cc49
--- /dev/null
+++ b/design/api/27.0.0-SNAPSHOT.ignore
@@ -0,0 +1,5 @@
+197ce1d
+88bc57e
+9761c3e
+86b38bf
+c6abd5e
diff --git a/development/unbundled-build b/development/unbundled-build
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/development/unbundled-build
diff --git a/fragment/src/main/java/android/support/v4/app/Fragment.java b/fragment/src/main/java/android/support/v4/app/Fragment.java
index 5b560cd..ce9bc4b 100644
--- a/fragment/src/main/java/android/support/v4/app/Fragment.java
+++ b/fragment/src/main/java/android/support/v4/app/Fragment.java
@@ -575,7 +575,6 @@
     /**
      * Return the {@link Context} this fragment is currently associated with.
      */
-    @Nullable
     public Context getContext() {
         return mHost == null ? null : mHost.getContext();
     }
@@ -585,7 +584,6 @@
      * May return {@code null} if the fragment is associated with a {@link Context}
      * instead.
      */
-    @Nullable
     final public FragmentActivity getActivity() {
         return mHost == null ? null : (FragmentActivity) mHost.getActivity();
     }
@@ -594,7 +592,6 @@
      * Return the host object of this fragment. May return {@code null} if the fragment
      * isn't currently being hosted.
      */
-    @Nullable
     final public Object getHost() {
         return mHost == null ? null : mHost.onGetHost();
     }
@@ -655,7 +652,6 @@
      * <p>If this Fragment is a child of another Fragment, the FragmentManager
      * returned here will be the parent's {@link #getChildFragmentManager()}.
      */
-    @Nullable
     final public FragmentManager getFragmentManager() {
         return mFragmentManager;
     }
diff --git a/graphics/OWNERS b/graphics/OWNERS
deleted file mode 100644
index 4e6deac..0000000
--- a/graphics/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ztenghui@google.com
\ No newline at end of file
diff --git a/graphics/drawable/animated/src/main/java/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java b/graphics/drawable/animated/src/main/java/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
index cff61bc..bc521cc 100644
--- a/graphics/drawable/animated/src/main/java/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
+++ b/graphics/drawable/animated/src/main/java/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
@@ -118,6 +118,9 @@
  *         <td>trimPathStart</td>
  *     </tr>
  *     <tr>
+ *         <td>trimPathEnd</td>
+ *     </tr>
+ *     <tr>
  *         <td>trimPathOffset</td>
  *     </tr>
  * </table>
diff --git a/graphics/drawable/animated/src/main/java/android/support/graphics/drawable/AnimatorInflaterCompat.java b/graphics/drawable/animated/src/main/java/android/support/graphics/drawable/AnimatorInflaterCompat.java
index cfededb..da522f6 100644
--- a/graphics/drawable/animated/src/main/java/android/support/graphics/drawable/AnimatorInflaterCompat.java
+++ b/graphics/drawable/animated/src/main/java/android/support/graphics/drawable/AnimatorInflaterCompat.java
@@ -463,7 +463,6 @@
         // the previously sampled contours' total length.
         for (int i = 0; i < numPoints; ++i) {
             pathMeasure.getPosTan(currentDistance, position, null);
-            pathMeasure.getPosTan(currentDistance, position, null);
 
             mX[i] = position[0];
             mY[i] = position[1];
diff --git a/include-composite-deps.gradle b/include-composite-deps.gradle
new file mode 100644
index 0000000..c8a795f
--- /dev/null
+++ b/include-composite-deps.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 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.
+ */
+
+
+// This file is part of a a workaround for https://github.com/gradle/gradle/issues/1909 to enable
+// including this Support Library build as an included build. See include-support-library.gradle
+// for usage instructions.
+
+boolean currentBuildIsRootBuild = (gradle.parent == null)
+
+// Add included builds. This only works if this is currently the root build, so this script should
+// be applied to several builds and will only enable itself when part of the root build.
+if (currentBuildIsRootBuild) {
+  String buildScriptDir = buildscript.sourceFile.parent
+  File externalRoot = new File(buildScriptDir, '../../external')
+
+  includeBuild(new File(externalRoot, 'doclava'))
+  includeBuild(new File(externalRoot, 'jdiff'))
+}
+
+
diff --git a/include-support-library.gradle b/include-support-library.gradle
new file mode 100644
index 0000000..fec8041
--- /dev/null
+++ b/include-support-library.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 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.
+ */
+
+// This file enables using Support Library in a Composite Build. More information about Composite
+// Builds can be found at https://docs.gradle.org/current/userguide/composite_builds.html .
+// This file is a workaround for https://github.com/gradle/gradle/issues/1909 .
+
+// Projects that want to include the build of Support Library should apply this file to their
+// settings.gradle . For example, if Support Library is at the file path ~/support-library, then
+// to include Support Library in your build, add this line to the bottom of your settings.gradle:
+//
+//   apply(from:'~/support-library/frameworks/support/include-support-library.gradle')
+
+String buildScriptDir = buildscript.sourceFile.parent
+
+// include any builds required by Support Library
+apply(from:new File(buildScriptDir, 'include-composite-deps.gradle'))
+
+// include Support Library itself
+includeBuild(buildScriptDir)
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt
index 40497d8..c3af1fb 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt
@@ -16,6 +16,7 @@
 
 package android.support.tools.jetifier.core.rules
 
+import android.support.tools.jetifier.core.transform.proguard.ProGuardType
 import com.google.gson.annotations.SerializedName
 import java.util.regex.Pattern
 
@@ -43,6 +44,28 @@
 
     private val fields = fieldSelectors.map { Pattern.compile("^$it$") }
 
+    /*
+     * Whether this is any type of an ignore rule.
+     */
+    fun isIgnoreRule() = isRuntimeIgnoreRule() || isPreprocessorOnlyIgnoreRule()
+
+    /*
+     * Whether this rules is an ignore rule.
+     *
+     * Any type matched to [from] will be in such case ignored by the preprocessor (thus missing
+     * from the map) but it will be also ignored during rewriting.
+     */
+    fun isRuntimeIgnoreRule() = to == "ignore"
+
+    /*
+     * Whether this rule is an ignore rule that should be used only in the preprocessor.
+     *
+     * That means that error is still thrown if [from] is found in a library that is being
+     * rewritten. Use this for types that are internal to support library. This is weaker version of
+     * [isRuntimeIgnoreRule].
+     */
+    fun isPreprocessorOnlyIgnoreRule() = to == "ignoreInPreprocessorOnly"
+
     /**
      * Rewrites the given java type. Returns null if this rule is not applicable for the given type.
      */
@@ -83,7 +106,7 @@
             return TypeRewriteResult.NOT_APPLIED
         }
 
-        if (to == "ignore") {
+        if (isIgnoreRule()) {
             return TypeRewriteResult.IGNORED
         }
 
@@ -95,6 +118,18 @@
         return TypeRewriteResult(JavaType(result))
     }
 
+    /*
+     * Returns whether this rule is an ignore rule and applies to the given proGuard type.
+     */
+    fun doesThisIgnoreProGuard(type: ProGuardType): Boolean {
+        if (!isIgnoreRule()) {
+            return false
+        }
+
+        val matcher = inputPattern.matcher(type.value)
+        return matcher.matches()
+    }
+
     override fun toString(): String {
         return "$inputPattern -> $outputPattern " + fields.joinToString { it.toString() }
     }
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 3f8af95..f18c876 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
@@ -18,6 +18,7 @@
 
 import android.support.tools.jetifier.core.config.Config
 import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.rules.RewriteRule.TypeRewriteResult
 import android.support.tools.jetifier.core.transform.proguard.ProGuardType
 import java.util.regex.Pattern
 
@@ -38,19 +39,26 @@
     var proGuardMappingNotFoundFailuresCount = 0
         private set
 
+    private var runtimeIgnoreRules = config.rewriteRules
+        .filter { it.isRuntimeIgnoreRule() }
+        .toTypedArray()
+
     /** Returns whether any errors were found during the transformation process */
     fun wasErrorFound() = mappingNotFoundFailuresCount > 0
+        || proGuardMappingNotFoundFailuresCount > 0
 
     /**
      * Returns whether the given type is eligible for rewrite.
      *
      * If not, the transformers should ignore it.
      */
-    fun isEligibleForRewrite(type: JavaType) : Boolean {
-        if (config.restrictToPackagePrefixes.isEmpty()) {
+    fun isEligibleForRewrite(type: JavaType): Boolean {
+        if (!isEligibleForRewriteInternal(type.fullName)) {
             return false
         }
-        return packagePrefixPattern.matcher(type.fullName).matches()
+
+        val isIgnored = runtimeIgnoreRules.any { it.apply(type) == TypeRewriteResult.IGNORED }
+        return !isIgnored
     }
 
     /**
@@ -60,11 +68,20 @@
      * like *.v7 are not matched by prefix support.v7. So don't rely on it and use
      * the [ProGuardTypesMap] as first.
      */
-    fun isEligibleForRewrite(type: ProGuardType) : Boolean {
+    fun isEligibleForRewrite(type: ProGuardType): Boolean {
+        if (!isEligibleForRewriteInternal(type.value)) {
+            return false
+        }
+
+        val isIgnored = runtimeIgnoreRules.any { it.doesThisIgnoreProGuard(type) }
+        return !isIgnored
+    }
+
+    private fun isEligibleForRewriteInternal(type: String): Boolean {
         if (config.restrictToPackagePrefixes.isEmpty()) {
             return false
         }
-        return packagePrefixPattern.matcher(type.value).matches()
+        return packagePrefixPattern.matcher(type).matches()
     }
 
     /**
diff --git a/jetifier/jetifier/core/src/main/resources/default.config b/jetifier/jetifier/core/src/main/resources/default.config
index a64d7eb..817d572 100644
--- a/jetifier/jetifier/core/src/main/resources/default.config
+++ b/jetifier/jetifier/core/src/main/resources/default.config
@@ -21,14 +21,18 @@
         # Ignore
         {
             from: "(.*)BuildConfig",
-            to: "ignore"
+            to: "ignoreInPreprocessorOnly"
         },
         {
             from: "android/support/v7/preference/R(.*)",
-            to: "ignore"
+            to: "ignoreInPreprocessorOnly"
         },
         {
             from: "(.*)/package-info",
+            to: "ignoreInPreprocessorOnly"
+        },
+        {
+            from: "android/support/test/(.*)",
             to: "ignore"
         },
 
diff --git a/jetifier/jetifier/core/src/main/resources/default.generated.config b/jetifier/jetifier/core/src/main/resources/default.generated.config
index d74d4c7..ec4e550 100644
--- a/jetifier/jetifier/core/src/main/resources/default.generated.config
+++ b/jetifier/jetifier/core/src/main/resources/default.generated.config
@@ -23,16 +23,21 @@
   "rules": [
     {
       "from": "(.*)BuildConfig",
-      "to": "ignore",
+      "to": "ignoreInPreprocessorOnly",
       "fieldSelectors": []
     },
     {
       "from": "android/support/v7/preference/R(.*)",
-      "to": "ignore",
+      "to": "ignoreInPreprocessorOnly",
       "fieldSelectors": []
     },
     {
       "from": "(.*)/package-info",
+      "to": "ignoreInPreprocessorOnly",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/test/(.*)",
       "to": "ignore",
       "fieldSelectors": []
     },
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt
index ecf9ccf..a2c9f29 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt
@@ -270,10 +270,11 @@
             .andIsComplete()
     }
 
-    @Test fun mapTwoTypes_shouldIgnoreOne() {
+    @Test fun mapTwoTypes_shouldIgnoreFirstTwo() {
         ScanTester
             .testThatRules(
                 RewriteRule("android/support/v7/(.*)", "ignore"),
+                RewriteRule("android/support/v8/(.*)", "ignoreInPreprocessorOnly"),
                 RewriteRule("android/support/v14/(.*)", "android/test/{0}")
             )
             .withAllowedPrefixes(
@@ -281,6 +282,7 @@
             )
             .forGivenTypes(
                 JavaType("android/support/v7/pref/Preference"),
+                JavaType("android/support/v8/pref/Preference"),
                 JavaType("android/support/v14/pref/Preference")
             )
             .mapInto(
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt
index 298d46c..9cf7e35 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2017 The Android Open Source Project
  *
@@ -22,7 +23,6 @@
 import com.google.common.truth.Truth
 import org.junit.Test
 
-
 class RewriteRuleTest {
 
     @Test fun noRegEx_shouldRewrite() {
@@ -75,6 +75,19 @@
             .into("A/C", "test")
     }
 
+    @Test fun typeRewrite_ignore() {
+        RuleTester
+            .testThatRule("A/B", "ignore")
+            .rewritesType("A/B")
+            .isIgnored()
+    }
+
+    @Test fun typeRewrite_ignoreInPreprocessor() {
+        RuleTester
+            .testThatRule("A/B", "ignoreInPreprocessorOnly")
+            .rewritesType("A/B")
+            .isIgnored()
+    }
 
     object RuleTester {
 
@@ -110,7 +123,6 @@
                 Truth.assertThat(result.result!!.owner.fullName).isEqualTo(expectedTypeName)
                 Truth.assertThat(result.result!!.name).isEqualTo(expectedFieldName)
             }
-
         }
 
         class RuleTesterFinalTypeStep(val fromType: String,
@@ -121,14 +133,20 @@
             fun into(expectedResult: String) {
                 val fieldRule = RewriteRule(fromType, toType, fieldSelectors)
                 val result = fieldRule.apply(JavaType(inputType))
-                Truth.assertThat(result).isNotNull()
 
                 Truth.assertThat(result).isNotNull()
                 Truth.assertThat(result.result!!.fullName).isEqualTo(expectedResult)
             }
 
-        }
-    }
+            fun isIgnored() {
+                val fieldRule = RewriteRule(fromType, toType, fieldSelectors)
+                val result = fieldRule.apply(JavaType(inputType))
 
+                Truth.assertThat(result).isNotNull()
+                Truth.assertThat(result.isIgnored).isTrue()
+            }
+        }
+
+    }
 }
 
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt
index 2c7d7e2..86b1599 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt
@@ -21,7 +21,7 @@
 class ClassFilterTest {
 
     @Test fun proGuard_classFilter() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -38,7 +38,7 @@
     }
 
     @Test fun proGuard_classFilter_newLineIgnored() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -57,7 +57,7 @@
     }
 
     @Test fun proGuard_classFilter_spacesRespected() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -74,7 +74,7 @@
     }
 
     @Test fun proGuard_classFilter_negation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt
index e64590f..dc3e470 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt
@@ -21,7 +21,7 @@
 class ClassSpecTest {
 
     @Test fun proGuard_classSpec_simple() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -39,7 +39,7 @@
     }
 
     @Test fun proGuard_classSpec_allExistingRules() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -71,7 +71,7 @@
     }
 
     @Test fun proGuard_classSpec_rulesModifiers() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -99,7 +99,7 @@
     }
 
     @Test fun proGuard_classSpec_extends() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -119,7 +119,7 @@
     }
 
     @Test fun proGuard_classSpec_modifiers_extends() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -137,7 +137,7 @@
     }
 
     @Test fun proGuard_classSpec_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -157,7 +157,7 @@
     }
 
     @Test fun proGuard_classSpec_annotation_extends() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -177,7 +177,7 @@
     }
 
     @Test fun proGuard_classSpec_annotation_extends_spaces() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -193,5 +193,4 @@
                 "-keep \t @test.Annotation \t public  class  *  extends test.Activity"
             )
     }
-
 }
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.kt
index 2832385..d08bbbe 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.kt
@@ -21,7 +21,7 @@
 class ClassSpecTest_FieldTypeSelector {
 
     @Test fun proGuard_fieldTypeSelector() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -46,7 +46,7 @@
     }
 
     @Test fun proGuard_fieldTypeSelector_modifiers() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -71,7 +71,7 @@
     }
 
     @Test fun proGuard_fieldTypeSelector_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -95,7 +95,7 @@
     }
 
     @Test fun proGuard_fieldTypeSelector_modifiers_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -121,7 +121,7 @@
     }
 
     @Test fun proGuard_fieldTypeSelector_modifiers_annotation_spaces() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -141,5 +141,4 @@
                 "}"
             )
     }
-
 }
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt
index 6f6a1f9..2a0f752 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt
@@ -21,7 +21,7 @@
 class ClassSpecTest_FieldsSelector {
 
     @Test fun proGuard_fieldsSelector_minimal() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -41,7 +41,7 @@
     }
 
     @Test fun proGuard_fieldsSelector_modifiers() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
             )
             .forGivenTypesMap(
@@ -63,7 +63,7 @@
     }
 
     @Test fun proGuard_fieldsSelector_modifiers_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -87,7 +87,7 @@
     }
 
     @Test fun proGuard_fieldsSelector_modifiers_annotation_spaces() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt
index 9a792cf..d681bda 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt
@@ -21,7 +21,7 @@
 class ClassSpecTest_MethodInitSelector {
 
     @Test fun proGuard_methodsInitSelector() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
             )
             .forGivenTypesMap(
@@ -39,7 +39,7 @@
     }
 
     @Test fun proGuard_methodsInitSelector_modifiers() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
             )
             .forGivenTypesMap(
@@ -63,7 +63,7 @@
     }
 
     @Test fun proGuard_methodsInitSelector_modifiers_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -89,7 +89,7 @@
     }
 
     @Test fun proGuard_methodInitSelector() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -118,7 +118,7 @@
     }
 
     @Test fun proGuard_methodInitSelector_modifiers() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -147,7 +147,7 @@
     }
 
     @Test fun proGuard_methodInitSelector_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -162,7 +162,8 @@
                 "  @support.Annotation <init>(*); \n" +
                 "  @support.Annotation <init>(...); \n" +
                 "  @keep.Me <init>(support.Activity); \n" +
-                "  @support.Annotation <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "  @support.Annotation <init>(support.Activity, support.Fragment, keep.Please);" +
+                " \n" +
                 "}"
             )
             .rewritesTo(
@@ -177,7 +178,7 @@
     }
 
     @Test fun proGuard_methodInitSelector_modifiers_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -192,7 +193,8 @@
                 "  @support.Annotation public static <init>(*); \n" +
                 "  @support.Annotation !public !static <init>(...); \n" +
                 "  @support.Annotation !private static <init>(support.Activity); \n" +
-                "  @support.Annotation public !abstract <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "  @support.Annotation public !abstract <init>(support.Activity, support.Fragment" +
+                ", keep.Please); \n" +
                 "}"
             )
             .rewritesTo(
@@ -201,13 +203,14 @@
                 "  @test.Annotation public static <init>(*); \n" +
                 "  @test.Annotation !public !static <init>(...); \n" +
                 "  @test.Annotation !private static <init>(test.Activity); \n" +
-                "  @test.Annotation public !abstract <init>(test.Activity, test.Fragment, keep.Please); \n" +
+                "  @test.Annotation public !abstract <init>(test.Activity, test.Fragment, " +
+                "keep.Please); \n" +
                 "}"
             )
     }
 
     @Test fun proGuard_methodInitSelector_modifiers_annotation_test() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -218,12 +221,14 @@
             )
             .testThatGivenProGuard(
                 "-keep public class * { \n" +
-                "  @support.Annotation  public  !abstract \t <init> ( support.Activity , support.Fragment, keep.Please); \n" +
+                "  @support.Annotation  public  !abstract \t <init> ( support.Activity , " +
+                "support.Fragment, keep.Please); \n" +
                 "}"
             )
             .rewritesTo(
                 "-keep public class * { \n" +
-                "  @test.Annotation  public  !abstract \t <init> (test.Activity, test.Fragment, keep.Please); \n" +
+                "  @test.Annotation  public  !abstract \t <init> (test.Activity, test.Fragment, " +
+                "keep.Please); \n" +
                 "}"
             )
     }
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt
index d9960b4..1cee765 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt
@@ -21,7 +21,7 @@
 class ClassSpecTest_MethodSelectorWithReturnType {
 
     @Test fun proGuard_methodReturnTypeSelector() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -54,7 +54,7 @@
     }
 
     @Test fun proGuard_methodReturnTypeSelector_voidResult() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -83,7 +83,7 @@
     }
 
     @Test fun proGuard_methodReturnTypeSelector_starResult() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -112,7 +112,7 @@
     }
 
     @Test fun proGuard_methodReturnTypeSelector_typeResult() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -141,7 +141,7 @@
     }
 
     @Test fun proGuard_methodReturnTypeSelector_typeResult_wildcards() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -170,7 +170,7 @@
     }
 
     @Test fun proGuard_methodReturnTypeSelector_typeResult_modifiers() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -184,7 +184,8 @@
                 "  public static support.Fragment get(...); \n" +
                 "  !public !static support.Fragment get(*); \n" +
                 "  private support.Fragment get(support.Activity); \n" +
-                "  public abstract support.Fragment get(support.Activity, support.Fragment, keep.Please); \n" +
+                "  public abstract support.Fragment get(support.Activity, support.Fragment, " +
+                "keep.Please); \n" +
                 "}"
             )
             .rewritesTo(
@@ -193,13 +194,14 @@
                 "  public static test.Fragment get(...); \n" +
                 "  !public !static test.Fragment get(*); \n" +
                 "  private test.Fragment get(test.Activity); \n" +
-                "  public abstract test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "  public abstract test.Fragment get(test.Activity, test.Fragment, keep.Please); " +
+                "\n" +
                 "}"
             )
     }
 
     @Test fun proGuard_methodReturnTypeSelector_typeResult_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -214,7 +216,8 @@
                 "  @support.Annotation support.Fragment get(...); \n" +
                 "  @support.Annotation support.Fragment get(*); \n" +
                 "  @keep.Me support.Fragment get(support.Activity); \n" +
-                "  @support.Annotation support.Fragment get(support.Activity, support.Fragment, keep.Please); \n" +
+                "  @support.Annotation support.Fragment get(support.Activity, support.Fragment, " +
+                "keep.Please); \n" +
                 "}"
             )
             .rewritesTo(
@@ -223,13 +226,14 @@
                 "  @test.Annotation test.Fragment get(...); \n" +
                 "  @test.Annotation test.Fragment get(*); \n" +
                 "  @keep.Me test.Fragment get(test.Activity); \n" +
-                "  @test.Annotation test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "  @test.Annotation test.Fragment get(test.Activity, test.Fragment, keep.Please" +
+                "); \n" +
                 "}"
             )
     }
 
     @Test fun proGuard_methodReturnTypeSelector_typeResult_modifiers_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -244,7 +248,8 @@
                 "  @support.Annotation public static support.Fragment get(...); \n" +
                 "  @support.Annotation !public !static support.Fragment get(*); \n" +
                 "  @support.Annotation private support.Fragment get(support.Activity); \n" +
-                "  @support.Annotation public abstract support.Fragment get(support.Activity, support.Fragment,  keep.Please); \n" +
+                "  @support.Annotation public abstract support.Fragment get(support.Activity, " +
+                "support.Fragment,  keep.Please); \n" +
                 "}"
             )
             .rewritesTo(
@@ -253,13 +258,14 @@
                 "  @test.Annotation public static test.Fragment get(...); \n" +
                 "  @test.Annotation !public !static test.Fragment get(*); \n" +
                 "  @test.Annotation private test.Fragment get(test.Activity); \n" +
-                "  @test.Annotation public abstract test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "  @test.Annotation public abstract test.Fragment get(test.Activity, " +
+                "test.Fragment, keep.Please); \n" +
                 "}"
             )
     }
 
     @Test fun proGuard_methodReturnTypeSelector_typeResult_modifiers_annotation_spaces() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -270,12 +276,14 @@
             )
             .testThatGivenProGuard(
                 "-keep public class * { \n" +
-                "  @support.Annotation  support.Fragment \t get(support.Activity ,  support.Fragment ,  keep.Please) ; \n" +
+                "  @support.Annotation  support.Fragment \t get(support.Activity ,  " +
+                "support.Fragment ,  keep.Please) ; \n" +
                 "}"
             )
             .rewritesTo(
                 "-keep public class * { \n" +
-                "  @test.Annotation  test.Fragment \t get(test.Activity, test.Fragment, keep.Please) ; \n" +
+                "  @test.Annotation  test.Fragment \t get(test.Activity, test.Fragment, " +
+                "keep.Please) ; \n" +
                 "}"
             )
     }
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt
index 21b8b8c..671be61 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt
@@ -21,7 +21,7 @@
 class ClassSpecTest_NamedCtorSelector {
 
     @Test fun proGuard_ctorSelector() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -50,7 +50,7 @@
     }
 
     @Test fun proGuard_ctorSelector_modifiers() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -64,7 +64,8 @@
                 "  public static support.Activity(...); \n" +
                 "  !private support.Activity(*); \n" +
                 "  !public !static support.Activity(support.Activity); \n" +
-                "  !protected support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "  !protected support.Activity(support.Activity, support.Fragment, keep.Please);" +
+                " \n" +
                 "}"
             )
             .rewritesTo(
@@ -79,7 +80,7 @@
     }
 
     @Test fun proGuard_ctorSelector_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -94,7 +95,8 @@
                 "  @support.Annotation support.Activity(...); \n" +
                 "  @support.Annotation support.Activity(*); \n" +
                 "  @support.Annotation support.Activity(support.Activity); \n" +
-                "  @support.Annotation support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "  @support.Annotation support.Activity(support.Activity, support.Fragment, " +
+                "keep.Please); \n" +
                 "}"
             )
             .rewritesTo(
@@ -109,7 +111,7 @@
     }
 
     @Test fun proGuard_ctorSelector_modifiers_annotation() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -124,7 +126,8 @@
                 "  @support.Annotation public static support.Activity(...); \n" +
                 "  @support.Annotation !private support.Activity(*); \n" +
                 "  @support.Annotation !public !static support.Activity(support.Activity); \n" +
-                "  @support.Annotation !protected support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "  @support.Annotation !protected support.Activity(support.Activity, " +
+                "support.Fragment, keep.Please); \n" +
                 "}"
             )
             .rewritesTo(
@@ -133,13 +136,14 @@
                 "  @test.Annotation public static test.Activity(...); \n" +
                 "  @test.Annotation !private test.Activity(*); \n" +
                 "  @test.Annotation !public !static test.Activity(test.Activity); \n" +
-                "  @test.Annotation !protected test.Activity(test.Activity, test.Fragment, keep.Please); \n" +
+                "  @test.Annotation !protected test.Activity(test.Activity, test.Fragment, " +
+                "keep.Please); \n" +
                 "}"
             )
     }
 
     @Test fun proGuard_ctorSelector_modifiers_annotation_spaces() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
index cae21d0..34d3a54 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
@@ -20,55 +20,61 @@
 import android.support.tools.jetifier.core.config.Config
 import android.support.tools.jetifier.core.map.TypesMap
 import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.rules.RewriteRule
 import android.support.tools.jetifier.core.transform.TransformationContext
 import com.google.common.truth.Truth
 import java.nio.charset.StandardCharsets
 import java.nio.file.Paths
 
-
 /**
  * Helper to test ProGuard rewriting logic using lightweight syntax.
  */
-object ProGuardTester {
+class ProGuardTester {
 
     private var javaTypes = emptyList<Pair<String, String>>()
+    private var rewriteRules = emptyList<Pair<String, String>>()
     private var proGuardTypes = emptyList<Pair<ProGuardType, ProGuardType>>()
     private var prefixes = emptyList<String>()
 
-    fun forGivenPrefixes(vararg prefixes: String) : ProGuardTester {
+    fun forGivenPrefixes(vararg prefixes: String): ProGuardTester {
         this.prefixes = prefixes.toList()
         return this
     }
 
-    fun forGivenTypesMap(vararg rules: Pair<String, String>) : ProGuardTester {
-        this.javaTypes = rules.toList()
+    fun forGivenTypesMap(vararg types: Pair<String, String>): ProGuardTester {
+        this.javaTypes = types.toList()
         return this
     }
 
-    fun forGivenProGuardMap(vararg rules: Pair<String, String>) : ProGuardTester {
+    fun forGivenRules(vararg rules: Pair<String, String>): ProGuardTester {
+        this.rewriteRules = rules.toList()
+        return this
+    }
+
+    fun forGivenProGuardMap(vararg rules: Pair<String, String>): ProGuardTester {
         this.proGuardTypes = rules.map {
             ProGuardType.fromDotNotation(it.first) to ProGuardType.fromDotNotation(it.second) }
             .toList()
         return this
     }
 
-    fun testThatGivenType(givenType: String) : ProGuardTesterForType {
+    fun testThatGivenType(givenType: String): ProGuardTesterForType {
         return ProGuardTesterForType(createConfig(), givenType)
     }
 
-    fun testThatGivenArguments(givenArgs: String) : ProGuardTesterForArgs {
+    fun testThatGivenArguments(givenArgs: String): ProGuardTesterForArgs {
         return ProGuardTesterForArgs(createConfig(), givenArgs)
     }
 
-    fun testThatGivenProGuard(given: String) : ProGuardTesterForFile {
+    fun testThatGivenProGuard(given: String): ProGuardTesterForFile {
         return ProGuardTesterForFile(createConfig(), given)
     }
 
-    private fun createConfig() : Config {
+    private fun createConfig(): Config {
         return Config(
             restrictToPackagePrefixes = prefixes,
-            rewriteRules = emptyList(),
-            pomRewriteRules =  emptyList(),
+            rewriteRules = rewriteRules.map { RewriteRule(it.first, it.second) },
+            pomRewriteRules = emptyList(),
             typesMap = TypesMap(
                 types = javaTypes.map { JavaType(it.first) to JavaType(it.second) }.toMap(),
                 fields = emptyMap()),
@@ -87,8 +93,8 @@
             val result = file.data.toString(StandardCharsets.UTF_8)
 
             Truth.assertThat(result).isEqualTo(expected)
+            Truth.assertThat(context.wasErrorFound()).isFalse()
         }
-
     }
 
     class ProGuardTesterForType(private val config: Config, private val given: String) {
@@ -99,8 +105,8 @@
             val result = mapper.replaceType(given)
 
             Truth.assertThat(result).isEqualTo(expectedType)
+            Truth.assertThat(context.wasErrorFound()).isFalse()
         }
-
     }
 
     class ProGuardTesterForArgs(private val config: Config, private val given: String) {
@@ -111,8 +117,7 @@
             val result = mapper.replaceMethodArgs(given)
 
             Truth.assertThat(result).isEqualTo(expectedArguments)
+            Truth.assertThat(context.wasErrorFound()).isFalse()
         }
     }
-
-}
-
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt
index 5e12aff..b1da4e2 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt
@@ -21,25 +21,25 @@
 class ProGuardTypesMapperTest {
 
     @Test fun proGuard_typeMapper_wildcard_simple() {
-        ProGuardTester
+        ProGuardTester()
             .testThatGivenType("*")
             .getsRewrittenTo("*")
     }
 
     @Test fun proGuard_typeMapper_wildcard_double() {
-        ProGuardTester
+        ProGuardTester()
             .testThatGivenType("**")
             .getsRewrittenTo("**")
     }
 
     @Test fun proGuard_typeMapper_wildcard_composed() {
-        ProGuardTester
+        ProGuardTester()
             .testThatGivenType("**/*")
             .getsRewrittenTo("**/*")
     }
 
     @Test fun proGuard_typeMapper_wildcard_viaMap() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -51,7 +51,7 @@
     }
 
     @Test fun proGuard_typeMapper_wildcard_viaMap2() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -63,7 +63,7 @@
     }
 
     @Test fun proGuard_typeMapper_wildcard_viaTypesMap() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -75,7 +75,7 @@
     }
 
     @Test fun proGuard_typeMapper_wildcard_notFoundInMap() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -87,7 +87,7 @@
     }
 
     @Test fun proGuard_typeMapper_differentPrefix_notRewritten() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -99,7 +99,7 @@
     }
 
     @Test fun proGuard_typeMapper_differentPrefix_wildcard_getsRewritten() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -111,7 +111,7 @@
     }
 
     @Test fun proGuard_typeMapper_innerClass() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -123,7 +123,7 @@
     }
 
     @Test fun proGuard_typeMapper_innerClass_wildcard() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -135,25 +135,25 @@
     }
 
     @Test fun proGuard_argsMapper_tripleDots() {
-        ProGuardTester
+        ProGuardTester()
             .testThatGivenArguments("...")
             .getRewrittenTo("...")
     }
 
     @Test fun proGuard_argsMapper_wildcard() {
-        ProGuardTester
+        ProGuardTester()
             .testThatGivenArguments("*")
             .getRewrittenTo("*")
     }
 
     @Test fun proGuard_argsMapper_wildcards() {
-        ProGuardTester
+        ProGuardTester()
             .testThatGivenArguments("**, **")
             .getRewrittenTo("**, **")
     }
 
     @Test fun proGuard_argsMapper_viaMaps() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -168,7 +168,7 @@
     }
 
     @Test fun proGuard_argsMapper_viaMaps_spaces() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -182,4 +182,41 @@
             .getRewrittenTo("test.Activity, test.v7.**, keep.Me")
     }
 
+    @Test fun proGuard_shouldIgnore() {
+        ProGuardTester()
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenRules(
+                "support/v7/Activity" to "ignore"
+            )
+            .testThatGivenType("support.v7.Activity")
+            .getsRewrittenTo("support.v7.Activity")
+    }
+
+    @Test fun proGuard_shouldIgnore_withWildcard() {
+        ProGuardTester()
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenRules(
+                "support/v7/(.*)" to "ignore"
+            )
+            .testThatGivenType("support.v7.**")
+            .getsRewrittenTo("support.v7.**")
+    }
+
+
+    @Test(expected = AssertionError::class)
+    fun proGuard_shouldNotIgnore() {
+        ProGuardTester()
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenRules(
+                "support/v7/Activity" to "ignoreInPreprocessor"
+            )
+            .testThatGivenType("support.v7.Activity")
+            .getsRewrittenTo("support.v7.Activity")
+    }
 }
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt
index 0542e7d..f9554f1 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt
@@ -21,7 +21,7 @@
 class ProguardSamplesTest {
 
     @Test fun proGuard_sample() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "android/app/",
                 "android/view/",
@@ -39,51 +39,51 @@
                 "android/webkit/JavascriptInterface" to "test/webkit/JavascriptInterface"
             )
             .testThatGivenProGuard(
-               "-injars      bin/classes \n" +
-               "-injars      libs \n" +
-               "-outjars     bin/classes-processed.jar \n" +
-               "-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar \n" +
-               "\n" +
-               "-dontpreverify \n" +
-               "-repackageclasses '' \n" +
-               "-allowaccessmodification \n" +
-               "-optimizations !code/simplification/arithmetic \n" +
-               "-keepattributes *Annotation* \n" +
-               "\n" +
-               "-keep public class * extends android.app.Activity \n" +
-               "-keep public class * extends android.app.Application \n" +
-               " \n" +
-               "-keep public class * extends android.view.View { \n" +
-               "      public <init>(android.content.Context); \n" +
-               "      public <init>(android.content.Context, android.util.AttributeSet); \n" +
-               "      public <init>(android.content.Context, android.util.AttributeSet, int); \n" +
-               "      public void set*(...); \n" +
-               "} \n" +
-               "\n" +
-               "-keepclasseswithmembers class * { \n" +
-               "    public <init>(android.content.Context, android.util.AttributeSet); \n" +
-               "} \n" +
-               "\n" +
-               "-keepclasseswithmembers class * { \n" +
-               "    public <init>(android.content.Context, android.util.AttributeSet, int); \n" +
-               "} \n" +
-               "\n" +
-               "-keepclassmembers class * extends android.content.Context { \n" +
-               "    public void *(android.view.View); \n" +
-               "    public void *(android.view.MenuItem); \n" +
-               "} \n" +
-               "\n" +
-               "-keepclassmembers class * implements android.os.Parcelable { \n" +
-               "    static ** CREATOR; \n" +
-               "} \n" +
-               "\n" +
-               "-keepclassmembers class **.R\$* { \n" +
-               "    public static <fields>; \n" +
-               "} \n" +
-               "\n" +
-               "-keepclassmembers class * { \n" +
-               "    @android.webkit.JavascriptInterface <methods>; \n" +
-               "} "
+                "-injars      bin/classes \n" +
+                "-injars      libs \n" +
+                "-outjars     bin/classes-processed.jar \n" +
+                "-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar \n" +
+                "\n" +
+                "-dontpreverify \n" +
+                "-repackageclasses '' \n" +
+                "-allowaccessmodification \n" +
+                "-optimizations !code/simplification/arithmetic \n" +
+                "-keepattributes *Annotation* \n" +
+                "\n" +
+                "-keep public class * extends android.app.Activity \n" +
+                "-keep public class * extends android.app.Application \n" +
+                " \n" +
+                "-keep public class * extends android.view.View { \n" +
+                "      public <init>(android.content.Context); \n" +
+                "      public <init>(android.content.Context, android.util.AttributeSet); \n" +
+                "      public <init>(android.content.Context, android.util.AttributeSet, int); \n" +
+                "      public void set*(...); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclasseswithmembers class * { \n" +
+                "    public <init>(android.content.Context, android.util.AttributeSet); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclasseswithmembers class * { \n" +
+                "    public <init>(android.content.Context, android.util.AttributeSet, int); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class * extends android.content.Context { \n" +
+                "    public void *(android.view.View); \n" +
+                "    public void *(android.view.MenuItem); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class * implements android.os.Parcelable { \n" +
+                "    static ** CREATOR; \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class **.R\$* { \n" +
+                "    public static <fields>; \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class * { \n" +
+                "    @android.webkit.JavascriptInterface <methods>; \n" +
+                "} "
             )
             .rewritesTo(
                 "-injars      bin/classes \n" +
@@ -135,7 +135,7 @@
     }
 
     @Test fun proGuard_sample2() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "android/support/v7/"
             )
@@ -161,7 +161,7 @@
     }
 
     @Test fun proGuard_sample3() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "android/support/design/",
                 "android/support/v7/"
@@ -191,7 +191,7 @@
     }
 
     @Test fun proGuard_sample4() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "android/support/design/",
                 "android/support/v7/",
@@ -238,7 +238,7 @@
     }
 
     @Test fun proGuard_sample5() {
-        ProGuardTester
+        ProGuardTester()
             .forGivenPrefixes(
                 "support/"
             )
@@ -270,5 +270,4 @@
                 "}"
             )
     }
-
 }
\ No newline at end of file
diff --git a/jetifier/jetifier/preprocessor/scripts/repo-links b/jetifier/jetifier/preprocessor/scripts/repo-links
index 961c149..eb1642a 100644
--- a/jetifier/jetifier/preprocessor/scripts/repo-links
+++ b/jetifier/jetifier/preprocessor/scripts/repo-links
@@ -48,5 +48,4 @@
 https://maven.google.com/com/android/support/support-vector-drawable/27.0.1/support-vector-drawable-27.0.1.aar
 https://maven.google.com/com/android/support/transition/27.0.1/transition-27.0.1.aar
 https://maven.google.com/com/android/support/wear/27.0.1/wear-27.0.1.aar
-https://maven.google.com/com/android/support/wearable/27.0.1/wearable-27.0.1.aar
 https://maven.google.com/com/android/support/constraint/constraint-layout/1.0.2/constraint-layout-1.0.2.aar
diff --git a/leanback/res/layout/lb_header.xml b/leanback/res/layout/lb_header.xml
index d86245c..7aeedc7 100644
--- a/leanback/res/layout/lb_header.xml
+++ b/leanback/res/layout/lb_header.xml
@@ -21,5 +21,6 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:focusable="true"
+    android:focusableInTouchMode="true"
     style="?headerStyle"
     />
diff --git a/leanback/res/values-mr/strings.xml b/leanback/res/values-mr/strings.xml
index 8629ef1..1fcb920 100644
--- a/leanback/res/values-mr/strings.xml
+++ b/leanback/res/values-mr/strings.xml
@@ -47,7 +47,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणवत्ता अक्षम करा"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"उपशीर्षके सक्षम करा"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"सबटायटल अक्षम करा"</string>
-    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्र मोडमध्ये चित्र प्रविष्ट करा"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्र मोडमध्ये चित्र एंटर करा"</string>
     <string name="lb_playback_time_separator" msgid="3208380806582304911">"/"</string>
     <string name="lb_playback_controls_shown" msgid="6382160135512023238">"मीडिया नियंत्रणे दर्शवली आहेत"</string>
     <string name="lb_playback_controls_hidden" msgid="8940984081242033574">"मीडिया नियंत्रणे लपलेली आहेत, दर्शवण्‍यासाठी d-pad दाबा"</string>
diff --git a/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java
index d3668ce..75769f5 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java
@@ -21,13 +21,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import android.app.Instrumentation;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
@@ -45,6 +48,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -100,6 +104,32 @@
         View row = mActivity.getBrowseTestFragment().getRowsFragment().getRowViewHolder(
                 mActivity.getBrowseTestFragment().getSelectedPosition()).view;
         PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.ViewStableOnScreen(row));
+        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.PollingCheckCondition() {
+            public boolean canProceed() {
+                return !mActivity.getBrowseTestFragment().isInHeadersTransition();
+            }
+        });
+    }
+
+    @Test
+    public void testTouchMode() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        ListRowPresenter.ViewHolder rowVh = (ListRowPresenter.ViewHolder) mActivity
+                .getBrowseTestFragment().getRowsFragment().getRowViewHolder(0);
+        View card = rowVh.getGridView().getChildAt(0);
+        tapView(card);
+        waitForHeaderTransitionFinished();
+        assertTrue(card.hasFocus());
+        assertTrue(card.isInTouchMode());
+        sendKeys(KeyEvent.KEYCODE_BACK);
+        waitForHeaderTransitionFinished();
+        assertTrue((mActivity.getBrowseTestFragment().getHeadersFragment()
+                .getVerticalGridView().getChildAt(0)).hasFocus());
     }
 
     @Test
@@ -282,6 +312,31 @@
         });
     }
 
+    static void tapView(View v) {
+        Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        int[] xy = new int[2];
+        v.getLocationOnScreen(xy);
+
+        final int viewWidth = v.getWidth();
+        final int viewHeight = v.getHeight();
+
+        final float x = xy[0] + (viewWidth / 2.0f);
+        float y = xy[1] + (viewHeight / 2.0f);
+
+        long downTime = SystemClock.uptimeMillis();
+        long eventTime = SystemClock.uptimeMillis();
+
+        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+                MotionEvent.ACTION_DOWN, x, y, 0);
+        inst.sendPointerSync(event);
+        inst.waitForIdleSync();
+
+        eventTime = SystemClock.uptimeMillis();
+        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+        inst.sendPointerSync(event);
+        inst.waitForIdleSync();
+    }
+
     private void sendKeys(int ...keys) {
         for (int i = 0; i < keys.length; i++) {
             InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
diff --git a/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
index a1964ed..de94551 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
@@ -18,13 +18,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import android.app.Instrumentation;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
@@ -42,6 +45,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -97,6 +101,32 @@
         View row = mActivity.getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder(
                 mActivity.getBrowseTestSupportFragment().getSelectedPosition()).view;
         PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.ViewStableOnScreen(row));
+        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.PollingCheckCondition() {
+            public boolean canProceed() {
+                return !mActivity.getBrowseTestSupportFragment().isInHeadersTransition();
+            }
+        });
+    }
+
+    @Test
+    public void testTouchMode() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        ListRowPresenter.ViewHolder rowVh = (ListRowPresenter.ViewHolder) mActivity
+                .getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder(0);
+        View card = rowVh.getGridView().getChildAt(0);
+        tapView(card);
+        waitForHeaderTransitionFinished();
+        assertTrue(card.hasFocus());
+        assertTrue(card.isInTouchMode());
+        sendKeys(KeyEvent.KEYCODE_BACK);
+        waitForHeaderTransitionFinished();
+        assertTrue((mActivity.getBrowseTestSupportFragment().getHeadersSupportFragment()
+                .getVerticalGridView().getChildAt(0)).hasFocus());
     }
 
     @Test
@@ -279,6 +309,31 @@
         });
     }
 
+    static void tapView(View v) {
+        Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        int[] xy = new int[2];
+        v.getLocationOnScreen(xy);
+
+        final int viewWidth = v.getWidth();
+        final int viewHeight = v.getHeight();
+
+        final float x = xy[0] + (viewWidth / 2.0f);
+        float y = xy[1] + (viewHeight / 2.0f);
+
+        long downTime = SystemClock.uptimeMillis();
+        long eventTime = SystemClock.uptimeMillis();
+
+        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+                MotionEvent.ACTION_DOWN, x, y, 0);
+        inst.sendPointerSync(event);
+        inst.waitForIdleSync();
+
+        eventTime = SystemClock.uptimeMillis();
+        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+        inst.sendPointerSync(event);
+        inst.waitForIdleSync();
+    }
+
     private void sendKeys(int ...keys) {
         for (int i = 0; i < keys.length; i++) {
             InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
index da7add8..83a2fa7 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
@@ -17,9 +17,9 @@
 
 package android.support.v17.leanback.app;
 
-import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
+import android.app.Activity;
 
 public class GuidedStepFragmentTestActivity extends Activity {
 
diff --git a/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
index d61fe51..26bdcdf 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
@@ -19,7 +19,6 @@
 package android.support.v17.leanback.app;
 
 import static org.junit.Assert.assertEquals;
-
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
diff --git a/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
index 098a8dc..50248e0 100644
--- a/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
+++ b/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
@@ -18,12 +18,11 @@
  */
 package android.support.v17.leanback.app;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
diff --git a/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java
index 9e981d3..56e22e1 100644
--- a/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java
+++ b/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java
@@ -20,7 +20,6 @@
 package android.support.v17.leanback.widget;
 
 import static org.junit.Assert.assertEquals;
-
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
index c5c5f17..0bd2f49 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
@@ -214,7 +214,7 @@
         return when (this) {
             TEXT -> listOf(env.elementUtils.getTypeElement("java.lang.String").asType())
             INTEGER -> withBoxedTypes(env, TypeKind.INT, TypeKind.BYTE, TypeKind.CHAR,
-                    TypeKind.BOOLEAN, TypeKind.LONG, TypeKind.SHORT)
+                    TypeKind.LONG, TypeKind.SHORT)
             REAL -> withBoxedTypes(env, TypeKind.DOUBLE, TypeKind.FLOAT)
             BLOB -> listOf(typeUtils.getArrayType(
                     typeUtils.getPrimitiveType(TypeKind.BYTE)))
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt
index 7abad86..62a24ad 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt
@@ -160,7 +160,11 @@
         }
         val targetTypes = targetTypeMirrorsFor(affinity)
         val binder = findTypeConverter(input, targetTypes) ?: return null
-        return CompositeAdapter(input, getAllColumnAdapters(binder.to).first(), binder, null)
+        // columnAdapter should not be null but we are receiving errors on crash in `first()` so
+        // this safeguard allows us to dispatch the real problem to the user (e.g. why we couldn't
+        // find the right adapter)
+        val columnAdapter = getAllColumnAdapters(binder.to).firstOrNull() ?: return null
+        return CompositeAdapter(input, columnAdapter, binder, null)
     }
 
     /**
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index 82a3583..7cb8b60 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -164,6 +164,12 @@
     @Query("SELECT COUNT(*) from user")
     public abstract int count();
 
+    @Query("SELECT mAdmin from User where mId = :uid")
+    public abstract boolean isAdmin(int uid);
+
+    @Query("SELECT mAdmin from User where mId = :uid")
+    public abstract LiveData<Boolean> isAdminLiveData(int uid);
+
     public void insertBothByRunnable(final User a, final User b) {
         mDatabase.runInTransaction(new Runnable() {
             @Override
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
index d78411f..d073598 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
@@ -315,6 +315,24 @@
         assertThat(weakLiveData.get(), nullValue());
     }
 
+    @Test
+    public void booleanLiveData() throws ExecutionException, InterruptedException,
+            TimeoutException {
+        User user = TestUtil.createUser(3);
+        user.setAdmin(false);
+        LiveData<Boolean> adminLiveData = mUserDao.isAdminLiveData(3);
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final TestObserver<Boolean> observer = new TestObserver<>();
+        observe(adminLiveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(nullValue()));
+        mUserDao.insert(user);
+        assertThat(observer.get(), is(false));
+        user.setAdmin(true);
+        mUserDao.insertOrReplace(user);
+        assertThat(observer.get(), is(true));
+    }
+
     private void observe(final LiveData liveData, final LifecycleOwner provider,
             final Observer observer) throws ExecutionException, InterruptedException {
         FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
index de45ebb..4ca81ad 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -267,6 +267,18 @@
     }
 
     @Test
+    public void returnBoolean() {
+        User user1 = TestUtil.createUser(1);
+        User user2 = TestUtil.createUser(2);
+        user1.setAdmin(true);
+        user2.setAdmin(false);
+        mUserDao.insert(user1);
+        mUserDao.insert(user2);
+        assertThat(mUserDao.isAdmin(1), is(true));
+        assertThat(mUserDao.isAdmin(2), is(false));
+    }
+
+    @Test
     public void findByCollateNoCase() {
         User user = TestUtil.createUser(3);
         user.setCustomField("abc");
diff --git a/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java b/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java
index d88c02f..0eb35f6 100644
--- a/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java
+++ b/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java
@@ -31,6 +31,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
 
 import org.junit.After;
 import org.junit.Test;
@@ -41,6 +42,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -209,6 +211,28 @@
         ));
     }
 
+    @Test
+    public void compatColumnTypes() {
+        // see:https://www.sqlite.org/datatype3.html 3.1
+        List<Pair<String, String>> testCases = Arrays.asList(
+                new Pair<>("TINYINT", "integer"),
+                new Pair<>("VARCHAR", "text"),
+                new Pair<>("DOUBLE", "real"),
+                new Pair<>("BOOLEAN", "numeric"),
+                new Pair<>("FLOATING POINT", "integer")
+        );
+        for (Pair<String, String> testCase : testCases) {
+            mDb = createDatabase(
+                    "CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT,"
+                            + "name " + testCase.first + ")");
+            TableInfo info = TableInfo.read(mDb, "foo");
+            assertThat(info, is(new TableInfo("foo",
+                    toMap(new TableInfo.Column("id", "INTEGER", false, 1),
+                            new TableInfo.Column("name", testCase.second, false, 0)),
+                    Collections.<TableInfo.ForeignKey>emptySet())));
+        }
+    }
+
     private static Map<String, TableInfo.Column> toMap(TableInfo.Column... columns) {
         Map<String, TableInfo.Column> result = new HashMap<>();
         for (TableInfo.Column column : columns) {
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
index 2f6b13d..db7af1d 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
@@ -608,8 +608,14 @@
                 }
                 boolean found = false;
                 for (int i = firstIndex; i != lastIndex; i += searchDirection) {
-                    int targetVersion = targetNodes.keyAt(i);
-                    if (targetVersion <= end && targetVersion > start) {
+                    final int targetVersion = targetNodes.keyAt(i);
+                    final boolean shouldAddToPath;
+                    if (upgrade) {
+                        shouldAddToPath = targetVersion <= end && targetVersion > start;
+                    } else {
+                        shouldAddToPath = targetVersion >= end && targetVersion < start;
+                    }
+                    if (shouldAddToPath) {
                         result.add(targetNodes.valueAt(i));
                         start = targetVersion;
                         found = true;
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java b/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java
index a115147..19d9853 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java
@@ -17,6 +17,7 @@
 package android.arch.persistence.room.util;
 
 import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.ColumnInfo;
 import android.database.Cursor;
 import android.os.Build;
 import android.support.annotation.NonNull;
@@ -28,6 +29,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
@@ -44,7 +46,8 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@SuppressWarnings({"WeakerAccess", "unused", "TryFinallyCanBeTryWithResources"})
+@SuppressWarnings({"WeakerAccess", "unused", "TryFinallyCanBeTryWithResources",
+        "SimplifiableIfStatement"})
 // if you change this class, you must change TableInfoWriter.kt
 public class TableInfo {
     /**
@@ -313,6 +316,14 @@
          */
         public final String type;
         /**
+         * The column type after it is normalized to one of the basic types according to
+         * https://www.sqlite.org/datatype3.html Section 3.1.
+         * <p>
+         * This is the value Room uses for equality check.
+         */
+        @ColumnInfo.SQLiteTypeAffinity
+        public final int affinity;
+        /**
          * Whether or not the column can be NULL.
          */
         public final boolean notNull;
@@ -337,6 +348,40 @@
             this.type = type;
             this.notNull = notNull;
             this.primaryKeyPosition = primaryKeyPosition;
+            this.affinity = findAffinity(type);
+        }
+
+        /**
+         * Implements https://www.sqlite.org/datatype3.html section 3.1
+         *
+         * @param type The type that was given to the sqlite
+         * @return The normalized type which is one of the 5 known affinities
+         */
+        @ColumnInfo.SQLiteTypeAffinity
+        private static int findAffinity(@Nullable String type) {
+            if (type == null) {
+                return ColumnInfo.BLOB;
+            }
+            String uppercaseType = type.toUpperCase(Locale.US);
+            if (uppercaseType.contains("INT")) {
+                return ColumnInfo.INTEGER;
+            }
+            if (uppercaseType.contains("CHAR")
+                    || uppercaseType.contains("CLOB")
+                    || uppercaseType.contains("TEXT")) {
+                return ColumnInfo.TEXT;
+            }
+            if (uppercaseType.contains("BLOB")) {
+                return ColumnInfo.BLOB;
+            }
+            if (uppercaseType.contains("REAL")
+                    || uppercaseType.contains("FLOA")
+                    || uppercaseType.contains("DOUB")) {
+                return ColumnInfo.REAL;
+            }
+            // sqlite returns NUMERIC here but it is like a catch all. We already
+            // have UNDEFINED so it is better to use UNDEFINED for consistency.
+            return ColumnInfo.UNDEFINED;
         }
 
         @Override
@@ -354,7 +399,7 @@
             if (!name.equals(column.name)) return false;
             //noinspection SimplifiableIfStatement
             if (notNull != column.notNull) return false;
-            return type != null ? type.equalsIgnoreCase(column.type) : column.type == null;
+            return affinity == column.affinity;
         }
 
         /**
@@ -369,7 +414,7 @@
         @Override
         public int hashCode() {
             int result = name.hashCode();
-            result = 31 * result + (type != null ? type.hashCode() : 0);
+            result = 31 * result + affinity;
             result = 31 * result + (notNull ? 1231 : 1237);
             result = 31 * result + primaryKeyPosition;
             return result;
@@ -380,6 +425,7 @@
             return "Column{"
                     + "name='" + name + '\''
                     + ", type='" + type + '\''
+                    + ", affinity='" + affinity + '\''
                     + ", notNull=" + notNull
                     + ", primaryKeyPosition=" + primaryKeyPosition
                     + '}';
@@ -472,7 +518,7 @@
         }
 
         @Override
-        public int compareTo(ForeignKeyWithSequence o) {
+        public int compareTo(@NonNull ForeignKeyWithSequence o) {
             final int idCmp = mId - o.mId;
             if (idCmp == 0) {
                 return mSequence - o.mSequence;
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java b/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java
index 33d96d8..2c9b9e7 100644
--- a/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java
+++ b/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java
@@ -109,6 +109,21 @@
     }
 
     @Test
+    public void migrationDowngrade() {
+        Migration m1_2 = new EmptyMigration(1, 2);
+        Migration m2_3 = new EmptyMigration(2, 3);
+        Migration m3_4 = new EmptyMigration(3, 4);
+        Migration m3_2 = new EmptyMigration(3, 2);
+        Migration m2_1 = new EmptyMigration(2, 1);
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .addMigrations(m1_2, m2_3, m3_4, m3_2, m2_1).build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
+        assertThat(migrations.findMigrationPath(3, 2), is(asList(m3_2)));
+        assertThat(migrations.findMigrationPath(3, 1), is(asList(m3_2, m2_1)));
+    }
+
+    @Test
     public void skipMigration() {
         Context context = mock(Context.class);
 
diff --git a/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml b/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml
index f469af2..4d1dca7 100644
--- a/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml
+++ b/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml
@@ -31,6 +31,12 @@
         android:title="@string/title_stylish_preference"
         android:summary="@string/summary_stylish_preference" />
 
+    <Preference
+        android:key="preference_with_icon"
+        android:title="Preference with icon"
+        android:summary="This preference has an icon"
+        android:icon="@android:drawable/ic_menu_camera" />
+
     <PreferenceCategory
         android:title="@string/inline_preferences">
 
@@ -39,6 +45,11 @@
             android:title="@string/title_checkbox_preference"
             android:summary="@string/summary_checkbox_preference" />
 
+        <SwitchPreference
+            android:key="switch_preference"
+            android:title="Switch preference"
+            android:summary="This is a switch" />
+
         <DropDownPreference
             android:key="dropdown_preference"
             android:title="@string/title_dropdown_preference"
diff --git a/samples/SupportSliceDemos/OWNERS b/samples/SupportSliceDemos/OWNERS
new file mode 100644
index 0000000..921a517
--- /dev/null
+++ b/samples/SupportSliceDemos/OWNERS
@@ -0,0 +1,2 @@
+jmonk@google.com
+madym@google.com
diff --git a/samples/SupportSliceDemos/build.gradle b/samples/SupportSliceDemos/build.gradle
new file mode 100644
index 0000000..9036e5e
--- /dev/null
+++ b/samples/SupportSliceDemos/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+
+plugins {
+    id("SupportAndroidTestAppPlugin")
+}
+
+dependencies {
+    implementation(project(":slices-view"))
+    implementation(project(":slices-builders"))
+    implementation(project(":slices-core"))
+    implementation(project(":design"))
+    implementation(project(":appcompat-v7"))
+    implementation(project(":cardview-v7"))
+    api(ARCH_LIFECYCLE_EXTENSIONS, libs.exclude_annotations_transitive)
+}
+
+android {
+    defaultConfig {
+        applicationId "com.example.androidx.slice.demos"
+    }
+}
+
+supportTestApp {
+    minSdkVersion = 26
+}
diff --git a/samples/SupportSliceDemos/src/main/AndroidManifest.xml b/samples/SupportSliceDemos/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5285c34
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/AndroidManifest.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.example.androidx.slice.demos">
+
+    <uses-sdk tools:overrideLibrary="androidx.app.slice.view, androidx.app.slice.builders, androidx.app.slice.core" />
+
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.BIND_SLICE" />
+
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".SliceBrowser"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="androidx.intent.SLICE_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <provider android:authorities="com.example.androidx.slice.demos"
+                  android:name=".SampleSliceProvider"
+                  android:grantUriPermissions="true">
+            <intent-filter>
+                <action android:name="androidx.intent.SLICE_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </provider>
+
+        <receiver
+            android:name=".SliceBroadcastReceiver"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="com.example.androidx.slice.action.*"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
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
new file mode 100644
index 0000000..11d9acb
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java
@@ -0,0 +1,304 @@
+/*
+ * 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.
+ */
+
+package com.example.androidx.slice.demos;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.text.format.DateUtils;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.builders.GridBuilder;
+import androidx.app.slice.builders.ListBuilder;
+import androidx.app.slice.builders.MessagingSliceBuilder;
+
+/**
+ * Examples of using slice template builders.
+ */
+public class SampleSliceProvider extends SliceProvider {
+
+    public static final String ACTION_WIFI_CHANGED =
+            "com.example.androidx.slice.action.WIFI_CHANGED";
+    public static final String ACTION_TOAST =
+            "com.example.androidx.slice.action.TOAST";
+    public static final String EXTRA_TOAST_MESSAGE = "com.example.androidx.extra.TOAST_MESSAGE";
+
+    public static final String[] URI_PATHS = {"message", "wifi", "note", "ride", "toggle",
+            "toggle2", "contact", "gallery", "weather"};
+
+    /**
+     * @return Uri with the provided path
+     */
+    public static Uri getUri(String path, Context context) {
+        return new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(context.getPackageName())
+                .appendPath(path)
+                .build();
+    }
+
+    @Override
+    public boolean onCreateSliceProvider() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Uri onMapIntentToUri(Intent intent) {
+        return getUri("wifi", getContext());
+    }
+
+    @Override
+    public Slice onBindSlice(Uri sliceUri) {
+        String path = sliceUri.getPath();
+        switch (path) {
+            case "/message":
+                return createMessagingSlice(sliceUri);
+            case "/wifi":
+                return createWifiSlice(sliceUri);
+            case "/note":
+                return createNoteSlice(sliceUri);
+            case "/ride":
+                return createRideSlice(sliceUri);
+            case "/toggle":
+                return createCustomToggleSlice(sliceUri);
+            case "/toggle2":
+                return createTwoCustomToggleSlices(sliceUri);
+            case "/contact":
+                return createContact(sliceUri);
+            case "/gallery":
+                return createGallery(sliceUri);
+            case "/weather":
+                return createWeather(sliceUri);
+        }
+        throw new IllegalArgumentException("Unknown uri " + sliceUri);
+    }
+
+    private Slice createWeather(Uri sliceUri) {
+        return new GridBuilder(getContext(), sliceUri)
+                .addCell(cb -> cb
+                        .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_1))
+                        .addText("MON")
+                        .addTitleText("69\u00B0"))
+                .addCell(cb -> cb
+                        .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_2))
+                        .addText("TUE")
+                        .addTitleText("71\u00B0"))
+                .addCell(cb -> cb
+                        .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_3))
+                        .addText("WED")
+                        .addTitleText("76\u00B0"))
+                .addCell(cb -> cb
+                        .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_4))
+                        .addText("THU")
+                        .addTitleText("72\u00B0"))
+                .addCell(cb -> cb
+                        .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_1))
+                        .addText("FRI")
+                        .addTitleText("68\u00B0"))
+                .build();
+    }
+
+    private Slice createGallery(Uri sliceUri) {
+        return new GridBuilder(getContext(), sliceUri)
+                .addCell(cb -> cb
+                    .addLargeImage(Icon.createWithResource(getContext(), R.drawable.slices_1)))
+                .addCell(cb -> cb
+                    .addLargeImage(Icon.createWithResource(getContext(), R.drawable.slices_2)))
+                .addCell(cb -> cb
+                    .addLargeImage(Icon.createWithResource(getContext(), R.drawable.slices_3)))
+                .addCell(cb -> cb
+                    .addLargeImage(Icon.createWithResource(getContext(), R.drawable.slices_4)))
+                .build();
+    }
+
+    private Slice createContact(Uri sliceUri) {
+        return new ListBuilder(getContext(), sliceUri)
+                .setColor(0xff3949ab)
+                .addRow(b -> b
+                        .setTitle("Mady Pitza")
+                        .setSubtitle("Frequently contacted contact")
+                        .setIsHeader(true)
+                        .addEndItem(Icon.createWithResource(getContext(), R.drawable.mady)))
+                .addGrid(b -> b
+                        .addCell(cb -> cb
+                            .addImage(Icon.createWithResource(getContext(), R.drawable.ic_call))
+                            .addText("Call")
+                            .setContentIntent(getBroadcastIntent(ACTION_TOAST, "call")))
+                        .addCell(cb -> cb
+                            .addImage(Icon.createWithResource(getContext(), R.drawable.ic_text))
+                            .addText("Text")
+                            .setContentIntent(getBroadcastIntent(ACTION_TOAST, "text")))
+                        .addCell(cb ->cb
+                            .addImage(Icon.createWithResource(getContext(), R.drawable.ic_video))
+                            .setContentIntent(getBroadcastIntent(ACTION_TOAST, "video"))
+                            .addText("Video"))
+                        .addCell(cb -> cb
+                            .addImage(Icon.createWithResource(getContext(), R.drawable.ic_email))
+                            .addText("Email")
+                            .setContentIntent(getBroadcastIntent(ACTION_TOAST, "email"))))
+                .build();
+    }
+
+    private Slice createMessagingSlice(Uri sliceUri) {
+        // TODO: Remote input.
+        return new MessagingSliceBuilder(getContext(), sliceUri)
+                .add(b -> b
+                        .addText("yo home \uD83C\uDF55, I emailed you the info")
+                        .addTimestamp(System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS)
+                        .addSource(Icon.createWithResource(getContext(), R.drawable.mady)))
+                .add(b -> b
+                        .addText("just bought my tickets")
+                        .addTimestamp(System.currentTimeMillis() - 10 * DateUtils.MINUTE_IN_MILLIS))
+                .add(b -> b
+                        .addText("yay! can't wait for getContext() weekend!\n"
+                                + "\uD83D\uDE00")
+                        .addTimestamp(System.currentTimeMillis() - 5 * DateUtils.MINUTE_IN_MILLIS)
+                        .addSource(Icon.createWithResource(getContext(), R.drawable.mady)))
+                .build();
+
+    }
+
+    private Slice createNoteSlice(Uri sliceUri) {
+        // TODO: Remote input.
+        return new ListBuilder(getContext(), sliceUri)
+                .setColor(0xfff4b400)
+                .addRow(b -> b
+                    .setTitle("Create new note")
+                    .setSubtitle("with this note taking app")
+                    .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_create),
+                            getBroadcastIntent(ACTION_TOAST, "create note"))
+                    .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_voice),
+                            getBroadcastIntent(ACTION_TOAST, "voice note"))
+                    .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_camera),
+                            getIntent("android.media.action.IMAGE_CAPTURE")))
+                .build();
+    }
+
+    private Slice createRideSlice(Uri sliceUri) {
+        return new ListBuilder(getContext(), sliceUri)
+                .setColor(0xff1b5e20)
+                .addSummaryRow(b -> b
+                    .setTitle("Get ride")
+                    .setSubtitle("Multiple cars 4 minutes away")
+                    .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_home),
+                            getBroadcastIntent(ACTION_TOAST, "home"))
+                    .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_work),
+                            getBroadcastIntent(ACTION_TOAST, "work")))
+                .addRow(b -> b
+                    .setContentIntent(getBroadcastIntent(ACTION_TOAST, "work"))
+                    .setTitle("Work")
+                    .setSubtitle("2 min")
+                    .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_work)))
+                .addRow(b -> b
+                    .setContentIntent(getBroadcastIntent(ACTION_TOAST, "home"))
+                    .setTitle("Home")
+                    .setSubtitle("2 hours 33 min via 101")
+                    .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_home)))
+                .addRow(b -> b
+                    .setContentIntent(getBroadcastIntent(ACTION_TOAST, "book ride"))
+                    .setTitle("Book ride")
+                    .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_car)))
+                .build();
+    }
+
+    private Slice createCustomToggleSlice(Uri sliceUri) {
+        return new ListBuilder(getContext(), sliceUri)
+                .setColor(0xffff4081)
+                .addRow(b -> b
+                    .setTitle("Custom toggle")
+                    .setSubtitle("It can support two states")
+                    .addToggle(getBroadcastIntent(ACTION_TOAST, "star toggled"),
+                            true /* isChecked */,
+                            Icon.createWithResource(getContext(), R.drawable.toggle_star)))
+                .build();
+    }
+
+    private Slice createTwoCustomToggleSlices(Uri sliceUri) {
+        return new ListBuilder(getContext(), sliceUri)
+                .setColor(0xffff4081)
+                .addRow(b -> b
+                        .setTitle("2 toggles")
+                        .setSubtitle("each supports two states")
+                        .addToggle(getBroadcastIntent(ACTION_TOAST, "first star toggled"),
+                                true /* isChecked */,
+                                Icon.createWithResource(getContext(), R.drawable.toggle_star))
+                        .addToggle(getBroadcastIntent(ACTION_TOAST, "second star toggled"),
+                                false /* isChecked */,
+                                Icon.createWithResource(getContext(), R.drawable.toggle_star)))
+                .build();
+    }
+
+    private Slice createWifiSlice(Uri sliceUri) {
+        // Get wifi state
+        WifiManager wifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+        int wifiState = wifiManager.getWifiState();
+        boolean wifiEnabled = false;
+        String state;
+        switch (wifiState) {
+            case WifiManager.WIFI_STATE_DISABLED:
+            case WifiManager.WIFI_STATE_DISABLING:
+                state = "disconnected";
+                break;
+            case WifiManager.WIFI_STATE_ENABLED:
+            case WifiManager.WIFI_STATE_ENABLING:
+                state = wifiManager.getConnectionInfo().getSSID();
+                wifiEnabled = true;
+                break;
+            case WifiManager.WIFI_STATE_UNKNOWN:
+            default:
+                state = ""; // just don't show anything?
+                break;
+        }
+        boolean finalWifiEnabled = wifiEnabled;
+        return new ListBuilder(getContext(), sliceUri)
+                .setColor(0xff4285f4)
+                .addRow(b -> b
+                    .setTitle("Wi-fi")
+                    .setTitleItem(Icon.createWithResource(getContext(), R.drawable.ic_wifi))
+                    .setSubtitle(state)
+                    .addToggle(getBroadcastIntent(ACTION_WIFI_CHANGED, null), finalWifiEnabled)
+                    .setContentIntent(getIntent(Settings.ACTION_WIFI_SETTINGS)))
+            .build();
+    }
+
+    private PendingIntent getIntent(String action) {
+        Intent intent = new Intent(action);
+        PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0);
+        return pi;
+    }
+
+    private PendingIntent getBroadcastIntent(String action, String message) {
+        Intent intent = new Intent(action);
+        intent.setClass(getContext(), SliceBroadcastReceiver.class);
+        // Ensure a new PendingIntent is created for each message.
+        int requestCode = 0;
+        if (message != null) {
+            intent.putExtra(EXTRA_TOAST_MESSAGE, message);
+            requestCode = message.hashCode();
+        }
+        return PendingIntent.getBroadcast(getContext(), requestCode, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+}
diff --git a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBroadcastReceiver.java b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBroadcastReceiver.java
new file mode 100644
index 0000000..05f0820
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBroadcastReceiver.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+package com.example.androidx.slice.demos;
+
+import static com.example.androidx.slice.demos.SampleSliceProvider.getUri;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.widget.Toast;
+
+import androidx.app.slice.core.SliceHints;
+
+/**
+ * Responds to actions performed on slices and notifies slices of updates in state changes.
+ */
+public class SliceBroadcastReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent i) {
+        String action = i.getAction();
+        switch (action) {
+            case SampleSliceProvider.ACTION_WIFI_CHANGED:
+                WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+                boolean newState = i.getBooleanExtra(SliceHints.EXTRA_TOGGLE_STATE,
+                        wm.isWifiEnabled());
+                wm.setWifiEnabled(newState);
+                // Wait a bit for wifi to update (TODO: is there a better way to do this?)
+                Handler h = new Handler();
+                h.postDelayed(() -> {
+                    context.getContentResolver().notifyChange(getUri("wifi", context), null);
+                }, 1000);
+                break;
+            case SampleSliceProvider.ACTION_TOAST:
+                String message = i.getExtras().getString(SampleSliceProvider.EXTRA_TOAST_MESSAGE,
+                        "no message");
+                Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+                break;
+        }
+    }
+}
diff --git a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
new file mode 100644
index 0000000..60956ae
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+
+package com.example.androidx.slice.demos;
+
+import static com.example.androidx.slice.demos.SampleSliceProvider.URI_PATHS;
+import static com.example.androidx.slice.demos.SampleSliceProvider.getUri;
+
+import android.arch.lifecycle.LiveData;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.BaseColumns;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.SearchView;
+import android.widget.SimpleCursorAdapter;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.widget.EventInfo;
+import androidx.app.slice.widget.SliceLiveData;
+import androidx.app.slice.widget.SliceView;
+
+/**
+ * Example use of SliceView. Uses a search bar to select/auto-complete a slice uri which is
+ * then displayed in the selected mode with SliceView.
+ */
+@RequiresApi(api = 28)
+public class SliceBrowser extends AppCompatActivity implements SliceView.SliceObserver {
+
+    private static final String TAG = "SlicePresenter";
+
+    private static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+    private static final boolean TEST_INTENT = false;
+
+    private ArrayList<Uri> mSliceUris = new ArrayList<Uri>();
+    private int mSelectedMode;
+    private ViewGroup mContainer;
+    private SearchView mSearchView;
+    private SimpleCursorAdapter mAdapter;
+    private SubMenu mTypeMenu;
+    private LiveData<Slice> mSliceLiveData;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_layout);
+
+        Toolbar toolbar = findViewById(R.id.search_toolbar);
+        setSupportActionBar(toolbar);
+
+        // Shows the slice
+        mContainer = findViewById(R.id.slice_preview);
+        mSearchView = findViewById(R.id.search_view);
+
+        final String[] from = new String[]{"uri"};
+        final int[] to = new int[]{android.R.id.text1};
+        mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1,
+                null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
+        mSearchView.setSuggestionsAdapter(mAdapter);
+        mSearchView.setIconifiedByDefault(false);
+        mSearchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
+            @Override
+            public boolean onSuggestionClick(int position) {
+                mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true);
+                return true;
+            }
+
+            @Override
+            public boolean onSuggestionSelect(int position) {
+                mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true);
+                return true;
+            }
+        });
+        mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(String s) {
+                addSlice(Uri.parse(s));
+                mSearchView.clearFocus();
+                return false;
+            }
+
+            @Override
+            public boolean onQueryTextChange(String s) {
+                populateAdapter(s);
+                return false;
+            }
+        });
+
+        mSelectedMode = (savedInstanceState != null)
+                ? savedInstanceState.getInt("SELECTED_MODE", SliceView.MODE_SHORTCUT)
+                : SliceView.MODE_SHORTCUT;
+        if (savedInstanceState != null) {
+            mSearchView.setQuery(savedInstanceState.getString("SELECTED_QUERY"), true);
+        }
+
+        // TODO: Listen for changes.
+        updateAvailableSlices();
+        if (TEST_INTENT) {
+            addSlice(new Intent("androidx.intent.SLICE_ACTION").setPackage(getPackageName()));
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        mTypeMenu = menu.addSubMenu("Type");
+        mTypeMenu.setIcon(R.drawable.ic_shortcut);
+        mTypeMenu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+        mTypeMenu.add("Shortcut");
+        mTypeMenu.add("Small");
+        mTypeMenu.add("Large");
+        menu.add("Auth");
+        super.onCreateOptionsMenu(menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getTitle().toString()) {
+            case "Auth":
+                authAllSlices();
+                return true;
+            case "Shortcut":
+                mTypeMenu.setIcon(R.drawable.ic_shortcut);
+                mSelectedMode = SliceView.MODE_SHORTCUT;
+                updateSliceModes();
+                return true;
+            case "Small":
+                mTypeMenu.setIcon(R.drawable.ic_small);
+                mSelectedMode = SliceView.MODE_SMALL;
+                updateSliceModes();
+                return true;
+            case "Large":
+                mTypeMenu.setIcon(R.drawable.ic_large);
+                mSelectedMode = SliceView.MODE_LARGE;
+                updateSliceModes();
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("SELECTED_MODE", mSelectedMode);
+        outState.putString("SELECTED_QUERY", mSearchView.getQuery().toString());
+    }
+
+    private void authAllSlices() {
+        List<ApplicationInfo> packages = getPackageManager().getInstalledApplications(0);
+        packages.forEach(info -> {
+            for (int i = 0; i < URI_PATHS.length; i++) {
+                grantUriPermission(info.packageName, getUri(URI_PATHS[i], getApplicationContext()),
+                        Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+            }
+        });
+    }
+
+    private void updateAvailableSlices() {
+        mSliceUris.clear();
+        List<PackageInfo> packageInfos = getPackageManager()
+                .getInstalledPackages(PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+        for (PackageInfo pi : packageInfos) {
+            ActivityInfo[] activityInfos = pi.activities;
+            if (activityInfos != null) {
+                for (ActivityInfo ai : activityInfos) {
+                    if (ai.metaData != null) {
+                        String sliceUri = ai.metaData.getString(SLICE_METADATA_KEY);
+                        if (sliceUri != null) {
+                            mSliceUris.add(Uri.parse(sliceUri));
+                        }
+                    }
+                }
+            }
+        }
+        for (int i = 0; i < URI_PATHS.length; i++) {
+            mSliceUris.add(getUri(URI_PATHS[i], getApplicationContext()));
+        }
+        populateAdapter(String.valueOf(mSearchView.getQuery()));
+    }
+
+    private void addSlice(Intent intent) {
+        SliceView v = new SliceView(getApplicationContext());
+        v.setSliceObserver(this);
+        v.setTag(intent);
+        if (mSliceLiveData != null) {
+            mSliceLiveData.removeObservers(this);
+        }
+        mContainer.removeAllViews();
+        mContainer.addView(v);
+        mSliceLiveData = SliceLiveData.fromIntent(this, intent);
+        v.setMode(mSelectedMode);
+        mSliceLiveData.observe(this, v);
+    }
+
+    private void addSlice(Uri uri) {
+        if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+            SliceView v = new SliceView(getApplicationContext());
+            v.setSliceObserver(this);
+            v.setTag(uri);
+            if (mSliceLiveData != null) {
+                mSliceLiveData.removeObservers(this);
+            }
+            mContainer.removeAllViews();
+            mContainer.addView(v);
+            mSliceLiveData = SliceLiveData.fromUri(this, uri);
+            v.setMode(mSelectedMode);
+            mSliceLiveData.observe(this, v);
+        } else {
+            Log.w(TAG, "Invalid uri, skipping slice: " + uri);
+        }
+    }
+
+    private void updateSliceModes() {
+        final int count = mContainer.getChildCount();
+        for (int i = 0; i < count; i++) {
+            ((SliceView) mContainer.getChildAt(i)).setMode(mSelectedMode);
+        }
+    }
+
+    private void populateAdapter(String query) {
+        final MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, "uri"});
+        ArrayMap<String, Integer> ranking = new ArrayMap<>();
+        ArrayList<String> suggestions = new ArrayList();
+        mSliceUris.forEach(uri -> {
+            String uriString = uri.toString();
+            if (uriString.contains(query)) {
+                ranking.put(uriString, uriString.indexOf(query));
+                suggestions.add(uriString);
+            }
+        });
+        suggestions.sort(new Comparator<String>() {
+            @Override
+            public int compare(String o1, String o2) {
+                return Integer.compare(ranking.get(o1), ranking.get(o2));
+            }
+        });
+        for (int i = 0; i < suggestions.size(); i++) {
+            c.addRow(new Object[]{i, suggestions.get(i)});
+        }
+        mAdapter.changeCursor(c);
+    }
+
+    @Override
+    public void onSliceAction(@NonNull EventInfo info, @NonNull SliceItem item) {
+        Log.w(TAG, "onSliceAction, info: " + info);
+        Log.w(TAG, "onSliceAction, sliceItem: \n" + item);
+    }
+}
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_call.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_call.xml
new file mode 100644
index 0000000..ebf9de6
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_call.xml
@@ -0,0 +1,9 @@
+<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="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_camera.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_camera.xml
new file mode 100644
index 0000000..74b6bf9
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_camera.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M 12 8.8 C 13.7673111995 8.8 15.2 10.2326888005 15.2 12 C 15.2 13.7673111995 13.7673111995 15.2 12 15.2 C 10.2326888005 15.2 8.8 13.7673111995 8.8 12 C 8.8 10.2326888005 10.2326888005 8.8 12 8.8 Z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1 .9 2 2 2h16c1.1 0 2-.9
+2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5
+5-2.24 5-5 5z" />
+    <path
+        android:pathData="M0 0h24v24H0z" />
+</vector>
\ No newline at end of file
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_car.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_car.xml
new file mode 100644
index 0000000..6bab660
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_car.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21 .42 -1.42 1.01L3 12v8c0
+.55 .45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55 .45 1 1 1h1c.55 0 1-.45
+1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5 .67 1.5
+1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5 .67 1.5
+1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z" />
+    <path
+        android:pathData="M0 0h24v24H0z" />
+</vector>
\ No newline at end of file
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_create.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_create.xml
new file mode 100644
index 0000000..d1666a8
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_create.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+<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="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_email.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_email.xml
new file mode 100644
index 0000000..ce97ab8
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_email.xml
@@ -0,0 +1,9 @@
+<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="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_home.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_home.xml
new file mode 100644
index 0000000..b278230
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_home.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
+    <path
+        android:pathData="M0 0h24v24H0z" />
+</vector>
\ No newline at end of file
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_large.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_large.xml
new file mode 100644
index 0000000..79ac590
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_large.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M34.0,2.02L14.0,2.0c-2.21,0.0 -4.0,1.79 -4.0,4.0l0.0,36.0c0.0,2.21 1.79,4.0 4.0,4.0l20.0,0.0c2.21,0.0 4.0,-1.79 4.0,-4.0L38.0,6.0c0.0,-2.21 -1.79,-3.98 -4.0,-3.98zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0l0.0,28.0z"/>
+    <path
+        android:strokeColor="#FF000000"
+        android:strokeWidth="2"
+        android:pathData="M16,18 l16,0 l0,10 l-16,0z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_shortcut.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_shortcut.xml
new file mode 100644
index 0000000..bf9572a
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_shortcut.xml
@@ -0,0 +1,34 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="48.0dp"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0"
+    android:width="48.0dp">
+    <path
+        android:fillColor="#e2e2e2"
+        android:pathData="M24.0,24.0m-19.0,0.0a19.0,19.0 0.0,1.0 1.0,38.0 0.0a19.0,19.0 0.0,1.0 1.0,-38.0 0.0"/>
+    <group
+        android:scaleX=".7"
+        android:scaleY=".7"
+        android:translateX="7.2"
+        android:translateY="7.2">
+
+        <path
+            android:fillColor="#ff000000"
+            android:pathData="M12.0,36.0c0.0,1.0 0.9,2.0 2.0,2.0l2.0,0.0l0.0,7.0c0.0,1.66 1.34,3.0 3.0,3.0s3.0,-1.34 3.0,-3.0l0.0,-7.0l4.0,0.0l0.0,7.0c0.0,1.66 1.34,3.0 3.0,3.0s3.0,-1.34 3.0,-3.0l0.0,-7.0l2.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L36.0,16.0L12.0,16.0l0.0,20.0zM7.0,16.0c-1.66,0.0 -3.0,1.34 -3.0,3.0l0.0,14.0c0.0,1.66 1.34,3.0 3.0,3.0s3.0,-1.34 3.0,-3.0L10.0,19.0c0.0,-1.66 -1.34,-3.0 -3.0,-3.0zm34.0,0.0c-1.66,0.0 -3.0,1.34 -3.0,3.0l0.0,14.0c0.0,1.66 1.34,3.0 3.0,3.0s3.0,-1.34 3.0,-3.0L44.0,19.0c0.0,-1.66 -1.34,-3.0 -3.0,-3.0zM31.06,4.32l2.61,-2.61c0.39,-0.3 0.39,-1.02 0.0,-1.41 -0.39,-0.39 -1.02,-0.39 -1.41,0.0L29.3,3.25C27.7,2.46 25.91,2.0 24.0,2.0c-1.92,0.0 -3.7,0.46 -5.33,1.26L15.0,0.29c-0.39,-0.39 -1.02,-0.39 -1.41,0.0 -0.3,0.39 -0.39,1.02 0.0,1.41l2.62,2.62C13.94,6.51 12.0,10.03 12.0,14.0l24.0,0.0c0.0,-3.98 -1.95,-7.5 -4.94,-9.68zM20.0,10.0l-2.0,0.0L18.0,8.0l2.0,0.0l0.0,2.0zm10.0,0.0l-2.0,0.0L28.0,8.0l2.0,0.0l0.0,2.0z"/>
+    </group>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_small.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_small.xml
new file mode 100644
index 0000000..8fd43df
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_small.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M34.0,2.02L14.0,2.0c-2.21,0.0 -4.0,1.79 -4.0,4.0l0.0,36.0c0.0,2.21 1.79,4.0 4.0,4.0l20.0,0.0c2.21,0.0 4.0,-1.79 4.0,-4.0L38.0,6.0c0.0,-2.21 -1.79,-3.98 -4.0,-3.98zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0l0.0,28.0z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16,18 l16,0 l0,4 l-16,0z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_star_off.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_star_off.xml
new file mode 100644
index 0000000..7f023b3
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_star_off.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+<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="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_star_on.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_star_on.xml
new file mode 100644
index 0000000..f3ca086
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_star_on.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+<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="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_text.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_text.xml
new file mode 100644
index 0000000..d2876bf
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_text.xml
@@ -0,0 +1,9 @@
+<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="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_video.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_video.xml
new file mode 100644
index 0000000..e23eac8
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_video.xml
@@ -0,0 +1,9 @@
+<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="M17,10.5V7c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1v-3.5l4,4v-11l-4,4z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_voice.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_voice.xml
new file mode 100644
index 0000000..8f465bb
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_voice.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+<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="M12,15c1.66,0 2.99,-1.34 2.99,-3L15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6v6c0,1.66 1.34,3 3,3zM17.3,12c0,3 -2.54,5.1 -5.3,5.1S6.7,15 6.7,12L5,12c0,3.42 2.72,6.23 6,6.72L11,22h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_wifi.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_wifi.xml
new file mode 100644
index 0000000..4fbfd60
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_wifi.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+<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="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
+        android:fillAlpha=".3"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M3.53,10.95l8.46,10.54 0.01,0.01 0.01,-0.01 8.46,-10.54C20.04,10.62 16.81,8 12,8c-4.81,0 -8.04,2.62 -8.47,2.95z"/>
+</vector>
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/ic_work.xml b/samples/SupportSliceDemos/src/main/res/drawable/ic_work.xml
new file mode 100644
index 0000000..6806402
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/ic_work.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:pathData="M0 0h24v24H0z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99 .89 -1.99 2L2
+19c0 1.11 .89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-6 0h-4V4h4v2z" />
+</vector>
\ No newline at end of file
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/mady.jpg b/samples/SupportSliceDemos/src/main/res/drawable/mady.jpg
new file mode 100644
index 0000000..8b61f1b
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/mady.jpg
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/slices_1.jpg b/samples/SupportSliceDemos/src/main/res/drawable/slices_1.jpg
new file mode 100644
index 0000000..31cc065
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/slices_1.jpg
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/slices_2.jpg b/samples/SupportSliceDemos/src/main/res/drawable/slices_2.jpg
new file mode 100644
index 0000000..adbe1d3
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/slices_2.jpg
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/slices_3.jpg b/samples/SupportSliceDemos/src/main/res/drawable/slices_3.jpg
new file mode 100644
index 0000000..6617019
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/slices_3.jpg
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/slices_4.jpg b/samples/SupportSliceDemos/src/main/res/drawable/slices_4.jpg
new file mode 100644
index 0000000..d00a8b3
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/slices_4.jpg
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/toggle_star.xml b/samples/SupportSliceDemos/src/main/res/drawable/toggle_star.xml
new file mode 100644
index 0000000..e964925
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/toggle_star.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/on"
+        android:state_checked="true"
+        android:drawable="@drawable/ic_star_on" />
+    <item
+        android:id="@+id/off"
+        android:drawable="@drawable/ic_star_off" />
+</selector>
\ No newline at end of file
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/weather_1.png b/samples/SupportSliceDemos/src/main/res/drawable/weather_1.png
new file mode 100644
index 0000000..7c4034e
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/weather_1.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/weather_2.png b/samples/SupportSliceDemos/src/main/res/drawable/weather_2.png
new file mode 100644
index 0000000..f1b6672
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/weather_2.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/weather_3.png b/samples/SupportSliceDemos/src/main/res/drawable/weather_3.png
new file mode 100644
index 0000000..a5db683
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/weather_3.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/drawable/weather_4.png b/samples/SupportSliceDemos/src/main/res/drawable/weather_4.png
new file mode 100644
index 0000000..0b7f3b0
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/drawable/weather_4.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/layout/activity_demo.xml b/samples/SupportSliceDemos/src/main/res/layout/activity_demo.xml
new file mode 100644
index 0000000..f557f40
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/layout/activity_demo.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.example.android.support.content.demos.ContentPagerDemoActivity">
+
+    <android.support.v7.widget.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+        android:background="?attr/colorPrimary"
+        android:theme="@style/AppTheme.AppBarOverlay"
+        app:popupTheme="@style/AppTheme.PopupOverlay"/>
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+</LinearLayout>
diff --git a/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml b/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml
new file mode 100644
index 0000000..6a087a3
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="top"
+    android:orientation="vertical" >
+
+    <FrameLayout
+        android:id="@+id/search_bar_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="#f2f2f2">
+        <android.support.v7.widget.CardView
+            android:id="@+id/search_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            app:cardCornerRadius="2dp"
+            app:cardBackgroundColor="?android:attr/colorBackground"
+            app:cardElevation="2dp">
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/search_toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="48dp"
+                android:background="?android:attr/selectableItemBackground"
+                android:contentInsetStart="0dp"
+                android:contentInsetStartWithNavigation="0dp"
+                android:theme="?android:attr/actionBarTheme">
+                <SearchView
+                    android:id="@+id/search_view"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:iconifiedByDefault="false"
+                    android:imeOptions="actionSearch|flagNoExtractUi"
+                    android:queryHint="content://..."
+                    android:searchIcon="@null"/>
+            </android.support.v7.widget.Toolbar>
+        </android.support.v7.widget.CardView>
+    </FrameLayout>
+
+
+    <ScrollView
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" >
+
+        <FrameLayout
+            android:id="@+id/slice_preview"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:orientation="vertical"
+            android:background="?android:attr/colorBackground"
+            android:elevation="10dp" />
+    </ScrollView>
+
+</LinearLayout>
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9a078e3
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..efc028a
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3af2608
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9bec2e6
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..34947cd
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/samples/SupportSliceDemos/src/main/res/values/colors.xml b/samples/SupportSliceDemos/src/main/res/values/colors.xml
new file mode 100644
index 0000000..86b4304
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  -->
+
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/samples/SupportSliceDemos/src/main/res/values/strings.xml b/samples/SupportSliceDemos/src/main/res/values/strings.xml
new file mode 100644
index 0000000..89583bd
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">Slice Browser</string>
+</resources>
diff --git a/samples/SupportSliceDemos/src/main/res/values/styles.xml b/samples/SupportSliceDemos/src/main/res/values/styles.xml
new file mode 100644
index 0000000..afb6bad
--- /dev/null
+++ b/samples/SupportSliceDemos/src/main/res/values/styles.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  -->
+
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
+
+</resources>
diff --git a/settings.gradle b/settings.gradle
index 059f57a..71a09e1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -115,6 +115,15 @@
 include ':webkit'
 project(':webkit').projectDir = new File(rootDir, 'webkit')
 
+include ':slices-core'
+project(':slices-core').projectDir = new File(rootDir, 'slices/core')
+
+include ':slices-view'
+project(':slices-view').projectDir = new File(rootDir, 'slices/view')
+
+include ':slices-builders'
+project(':slices-builders').projectDir = new File(rootDir, 'slices/builders')
+
 include ':webkit-codegen'
 project(':webkit-codegen').projectDir = new File(rootDir, 'webkit-codegen')
 
@@ -176,6 +185,10 @@
 
 include ':support-car-demos'
 project(':support-car-demos').projectDir = new File(samplesRoot, 'SupportCarDemos')
+
+include ':support-slices-demos'
+project(':support-slices-demos').projectDir = new File(samplesRoot, 'SupportSliceDemos')
+
 /////////////////////////////
 //
 // Testing libraries
@@ -212,12 +225,9 @@
 //
 /////////////////////////////
 
+apply(from: 'include-composite-deps.gradle')
 File externalRoot = new File(rootDir, '../../external')
 
-includeBuild new File(externalRoot, 'doclava')
-
-includeBuild new File(externalRoot, 'jdiff')
-
 include ':noto-emoji-compat'
 project(':noto-emoji-compat').projectDir = new File(externalRoot, 'noto-fonts/emoji-compat')
 
diff --git a/slices/Android.mk b/slices/Android.mk
new file mode 100644
index 0000000..b622eed
--- /dev/null
+++ b/slices/Android.mk
@@ -0,0 +1,18 @@
+# Copyright (C) 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Build all slices libraries
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/slices/OWNERS b/slices/OWNERS
new file mode 100644
index 0000000..921a517
--- /dev/null
+++ b/slices/OWNERS
@@ -0,0 +1,2 @@
+jmonk@google.com
+madym@google.com
diff --git a/slices/builders/Android.mk b/slices/builders/Android.mk
new file mode 100644
index 0000000..ca66cbe
--- /dev/null
+++ b/slices/builders/Android.mk
@@ -0,0 +1,38 @@
+# Copyright (C) 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-slices-core \
+#       android-slices-builders
+#
+# in their makefiles to include the resources and their dependencies in their package.
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := android-slices-builders
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under,src/main/java) \
+    $(call all-Iaidl-files-under,src/main/java)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/src/main/res
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations \
+    android-slices-core
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/slices/builders/api/current.txt b/slices/builders/api/current.txt
new file mode 100644
index 0000000..7083e34
--- /dev/null
+++ b/slices/builders/api/current.txt
@@ -0,0 +1,54 @@
+package androidx.app.slice.builders {
+
+  public class GridBuilder extends androidx.app.slice.builders.TemplateSliceBuilder {
+    ctor public GridBuilder(android.net.Uri);
+    ctor public GridBuilder(androidx.app.slice.builders.TemplateSliceBuilder);
+    method public androidx.app.slice.builders.GridBuilder addCell(androidx.app.slice.builders.GridBuilder.CellBuilder);
+    method public androidx.app.slice.builders.GridBuilder addCell(java.util.function.Consumer<androidx.app.slice.builders.GridBuilder.CellBuilder>);
+  }
+
+  public static final class GridBuilder.CellBuilder extends androidx.app.slice.builders.TemplateSliceBuilder {
+    ctor public GridBuilder.CellBuilder(androidx.app.slice.builders.GridBuilder);
+    ctor public GridBuilder.CellBuilder(android.net.Uri);
+    method public androidx.app.slice.builders.GridBuilder.CellBuilder addImage(android.graphics.drawable.Icon);
+    method public androidx.app.slice.builders.GridBuilder.CellBuilder addLargeImage(android.graphics.drawable.Icon);
+    method public androidx.app.slice.builders.GridBuilder.CellBuilder addText(java.lang.CharSequence);
+    method public androidx.app.slice.builders.GridBuilder.CellBuilder addTitleText(java.lang.CharSequence);
+    method public androidx.app.slice.builders.GridBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
+  }
+
+  public class ListBuilder extends androidx.app.slice.builders.TemplateSliceBuilder {
+    ctor public ListBuilder(android.net.Uri);
+    method public androidx.app.slice.builders.ListBuilder addGrid(androidx.app.slice.builders.GridBuilder);
+    method public androidx.app.slice.builders.ListBuilder addGrid(java.util.function.Consumer<androidx.app.slice.builders.GridBuilder>);
+    method public androidx.app.slice.builders.ListBuilder addRow(androidx.app.slice.builders.ListBuilder.RowBuilder);
+    method public androidx.app.slice.builders.ListBuilder addRow(java.util.function.Consumer<androidx.app.slice.builders.ListBuilder.RowBuilder>);
+    method public androidx.app.slice.builders.ListBuilder addSummaryRow(androidx.app.slice.builders.ListBuilder.RowBuilder);
+    method public androidx.app.slice.builders.ListBuilder addSummaryRow(java.util.function.Consumer<androidx.app.slice.builders.ListBuilder.RowBuilder>);
+  }
+
+  public static class ListBuilder.RowBuilder extends androidx.app.slice.builders.TemplateSliceBuilder {
+    ctor public ListBuilder.RowBuilder(androidx.app.slice.builders.ListBuilder);
+    ctor public ListBuilder.RowBuilder(android.net.Uri);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder addEndItem(long);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder addEndItem(android.graphics.drawable.Icon);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder addEndItem(android.graphics.drawable.Icon, android.app.PendingIntent);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder addToggle(android.app.PendingIntent, boolean);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder addToggle(android.app.PendingIntent, boolean, android.graphics.drawable.Icon);
+    method public void apply(androidx.app.slice.Slice.Builder);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder setContentIntent(android.app.PendingIntent);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder setIsHeader(boolean);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder setSubtitle(java.lang.CharSequence);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder setTitle(java.lang.CharSequence);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder setTitleItem(long);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder setTitleItem(android.graphics.drawable.Icon);
+    method public androidx.app.slice.builders.ListBuilder.RowBuilder setTitleItem(android.graphics.drawable.Icon, android.app.PendingIntent);
+  }
+
+  public abstract class TemplateSliceBuilder {
+    ctor public TemplateSliceBuilder(android.net.Uri);
+    method public androidx.app.slice.Slice build();
+  }
+
+}
+
diff --git a/slices/builders/build.gradle b/slices/builders/build.gradle
new file mode 100644
index 0000000..ae7407b
--- /dev/null
+++ b/slices/builders/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryVersions
+import android.support.LibraryGroups
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    implementation(project(":slices-core"))
+    implementation project(":support-annotations")
+}
+
+supportLibrary {
+    name = "Slice builders"
+    publish = true
+    mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+    mavenGroup = LibraryGroups.SLICES
+    inceptionYear = "2017"
+    description = "A set of builders to create templates using SliceProvider APIs"
+    minSdkVersion = 24
+}
diff --git a/slices/builders/lint-baseline.xml b/slices/builders/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/slices/builders/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/slices/builders/src/main/AndroidManifest.xml b/slices/builders/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..75e31f1
--- /dev/null
+++ b/slices/builders/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.app.slice.builders">
+    <uses-sdk android:minSdkVersion="28"/>
+</manifest>
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/GridBuilder.java b/slices/builders/src/main/java/androidx/app/slice/builders/GridBuilder.java
new file mode 100644
index 0000000..32f8a69
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/GridBuilder.java
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.builders;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+
+import java.util.function.Consumer;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpecs;
+import androidx.app.slice.builders.impl.GridBuilderBasicImpl;
+import androidx.app.slice.builders.impl.GridBuilderListV1Impl;
+import androidx.app.slice.builders.impl.TemplateBuilderImpl;
+
+/**
+ * Builder to construct a row of slice content in a grid format.
+ * <p>
+ * A grid row is composed of cells, each cell can have a combination of text and images. For more
+ * details see {@link CellBuilder}.
+ * </p>
+ */
+public class GridBuilder extends TemplateSliceBuilder {
+
+    private androidx.app.slice.builders.impl.GridBuilder mImpl;
+
+    /**
+     * Create a builder which will construct a slice displayed in a grid format.
+     * @param uri Uri to tag for this slice.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public GridBuilder(@NonNull Context context, @NonNull Uri uri) {
+        super(new Slice.Builder(uri), context);
+    }
+
+    /**
+     * Create a builder which will construct a slice displayed in a grid format.
+     * @param parent The builder constructing the parent slice.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public GridBuilder(@NonNull ListBuilder parent) {
+        super(parent.getImpl().createGridBuilder());
+    }
+
+    /**
+     */
+    public GridBuilder(@NonNull Uri uri) {
+        super(uri);
+        throw new RuntimeException("Stub, to be removed");
+    }
+
+    /**
+     */
+    public GridBuilder(@NonNull TemplateSliceBuilder z) {
+        super((Uri) null);
+        throw new RuntimeException("Stub, to be removed");
+    }
+
+    @Override
+    @NonNull
+    public Slice build() {
+        return mImpl.buildIndividual();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    @Override
+    protected TemplateBuilderImpl selectImpl() {
+        if (checkCompatible(SliceSpecs.GRID)) {
+            return new GridBuilderListV1Impl(getBuilder(), SliceSpecs.GRID);
+        } else if (checkCompatible(SliceSpecs.BASIC)) {
+            return new GridBuilderBasicImpl(getBuilder(), SliceSpecs.GRID);
+        }
+        return null;
+    }
+
+    @Override
+    void setImpl(TemplateBuilderImpl impl) {
+        mImpl = (androidx.app.slice.builders.impl.GridBuilder) impl;
+    }
+
+    /**
+     * Add a cell to the grid builder.
+     */
+    @NonNull
+    public GridBuilder addCell(@NonNull CellBuilder builder) {
+        mImpl.addCell((TemplateBuilderImpl) builder.mImpl);
+        return this;
+    }
+
+    /**
+     * Add a cell to the grid builder.
+     */
+    @RequiresApi(Build.VERSION_CODES.N)
+    @NonNull
+    public GridBuilder addCell(@NonNull Consumer<CellBuilder> c) {
+        CellBuilder b = new CellBuilder(this);
+        c.accept(b);
+        return addCell(b);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public androidx.app.slice.builders.impl.GridBuilder getImpl() {
+        return mImpl;
+    }
+
+    /**
+     * Sub-builder to construct a cell to be displayed in a grid.
+     * <p>
+     * Content added to a cell will be displayed in order vertically, for example the below code
+     * would construct a cell with "First text", and image below it, and then "Second text" below
+     * the image.
+     *
+     * <pre class="prettyprint">
+     * CellBuilder cb = new CellBuilder(parent, sliceUri);
+     * cb.addText("First text")
+     *   .addImage(middleIcon)
+     *   .addText("Second text");
+     * </pre>
+     *
+     * A cell can have at most two text items and one image.
+     * </p>
+     */
+    public static final class CellBuilder extends TemplateSliceBuilder {
+        private androidx.app.slice.builders.impl.GridBuilder.CellBuilder mImpl;
+
+        /**
+         * Create a builder which will construct a slice displayed as a cell in a grid.
+         * @param parent The builder constructing the parent slice.
+         */
+        public CellBuilder(@NonNull GridBuilder parent) {
+            super(parent.mImpl.createGridBuilder());
+        }
+
+        /**
+         */
+        public CellBuilder(@NonNull Uri uri) {
+            super(uri);
+            throw new RuntimeException("Stub, to be removed");
+        }
+
+        /**
+         * Create a builder which will construct a slice displayed as a cell in a grid.
+         * @param uri Uri to tag for this slice.
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public CellBuilder(@NonNull GridBuilder parent, @NonNull Uri uri) {
+            super(parent.mImpl.createGridBuilder(uri));
+        }
+
+        @Override
+        void setImpl(TemplateBuilderImpl impl) {
+            mImpl = (androidx.app.slice.builders.impl.GridBuilder.CellBuilder) impl;
+        }
+
+        /**
+         * Adds text to the cell. There can be at most two text items, the first two added
+         * will be used, others will be ignored.
+         */
+        @NonNull
+        public CellBuilder addText(@NonNull CharSequence text) {
+            mImpl.addText(text);
+            return this;
+        }
+
+        /**
+         * Adds text to the cell. Text added with this method will be styled as a title.
+         * There can be at most two text items, the first two added will be used, others
+         * will be ignored.
+         */
+        @NonNull
+        public CellBuilder addTitleText(@NonNull CharSequence text) {
+            mImpl.addTitleText(text);
+            return this;
+        }
+
+        /**
+         * Adds an image to the cell that should be displayed as large as the cell allows.
+         * There can be at most one image, the first one added will be used, others will be ignored.
+         *
+         * @param image the image to display in the cell.
+         */
+        @NonNull
+        public CellBuilder addLargeImage(@NonNull Icon image) {
+            mImpl.addLargeImage(image);
+            return this;
+        }
+
+        /**
+         * Adds an image to the cell. There can be at most one image, the first one added
+         * will be used, others will be ignored.
+         *
+         * @param image the image to display in the cell.
+         */
+        @NonNull
+        public CellBuilder addImage(@NonNull Icon image) {
+            mImpl.addImage(image);
+            return this;
+        }
+
+        /**
+         * Sets the action to be invoked if the user taps on this cell in the row.
+         */
+        @NonNull
+        public CellBuilder setContentIntent(@NonNull PendingIntent intent) {
+            mImpl.setContentIntent(intent);
+            return this;
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/ListBuilder.java b/slices/builders/src/main/java/androidx/app/slice/builders/ListBuilder.java
new file mode 100644
index 0000000..6c31285
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/ListBuilder.java
@@ -0,0 +1,442 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.builders;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+
+import java.util.function.Consumer;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpecs;
+import androidx.app.slice.builders.impl.ListBuilderBasicImpl;
+import androidx.app.slice.builders.impl.ListBuilderV1Impl;
+import androidx.app.slice.builders.impl.TemplateBuilderImpl;
+
+/**
+ * Builder to construct slice content in a list format.
+ * <p>
+ * Use this builder for showing rows of content which is composed of text, images, and actions. For
+ * more details see {@link RowBuilder}.
+ * </p>
+ * <p>
+ * Slices can be displayed in different formats:
+ * <ul>
+ *     <li>Shortcut - The slice is displayed as an icon with a text label.</li>
+ *     <li>Small - Only a single row of content is displayed in small format, to specify which
+ *         row to display in small format see {@link #addSummaryRow(RowBuilder)}.</li>
+ *     <li>Large - As many rows of content are shown as possible. If the presenter of the slice
+ *         allows scrolling then all rows of content will be displayed in a scrollable view.</li>
+ * </ul>
+ * </p>
+ *
+ * @see RowBuilder
+ */
+public class ListBuilder extends TemplateSliceBuilder {
+
+    private boolean mHasSummary;
+    private androidx.app.slice.builders.impl.ListBuilder mImpl;
+
+    /**
+     */
+    public ListBuilder(@NonNull Uri uri) {
+        super(uri);
+        throw new RuntimeException("Stub, to be removed");
+    }
+
+    /**
+     * Create a builder which will construct a slice that will display rows of content.
+     * @param uri Uri to tag for this slice.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public ListBuilder(@NonNull Context context, @NonNull Uri uri) {
+        super(context, uri);
+    }
+
+    @Override
+    void setImpl(TemplateBuilderImpl impl) {
+        mImpl = (androidx.app.slice.builders.impl.ListBuilder) impl;
+    }
+
+    /**
+     * Add a row to list builder.
+     */
+    @NonNull
+    public ListBuilder addRow(@NonNull RowBuilder builder) {
+        mImpl.addRow((TemplateBuilderImpl) builder.mImpl);
+        return this;
+    }
+
+    /**
+     * Add a row the list builder.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    @NonNull
+    public ListBuilder addRow(@NonNull Consumer<RowBuilder> c) {
+        RowBuilder b = new RowBuilder(this);
+        c.accept(b);
+        return addRow(b);
+
+    }
+
+    /**
+     * Add a grid row to the list builder.
+     */
+    @NonNull
+    public ListBuilder addGrid(@NonNull GridBuilder builder) {
+        mImpl.addGrid((TemplateBuilderImpl) builder.getImpl());
+        return this;
+    }
+
+    /**
+     * Add a grid row to the list builder.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    @NonNull
+    public ListBuilder addGrid(@NonNull Consumer<GridBuilder> c) {
+        GridBuilder b = new GridBuilder(this);
+        c.accept(b);
+        return addGrid(b);
+    }
+
+    /**
+     * Add a summary row for this template. The summary content is displayed
+     * when the slice is displayed in small format.
+     * <p>
+     * Only one summary row can be added, this throws {@link IllegalArgumentException} if
+     * called more than once.
+     * </p>
+     */
+    public ListBuilder addSummaryRow(RowBuilder builder) {
+        if (mHasSummary) {
+            throw new IllegalArgumentException("Trying to add summary row when one has "
+                    + "already been added");
+        }
+        mImpl.addSummaryRow((TemplateBuilderImpl) builder.mImpl);
+        mHasSummary = true;
+        return this;
+    }
+
+    /**
+     * Add a summary row for this template. The summary content is displayed
+     * when the slice is displayed in small format.
+     * <p>
+     * Only one summary row can be added, this throws {@link IllegalArgumentException} if
+     * called more than once.
+     * </p>
+     */
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    public ListBuilder addSummaryRow(Consumer<RowBuilder> c) {
+        if (mHasSummary) {
+            throw new IllegalArgumentException("Trying to add summary row when one has "
+                    + "already been added");
+        }
+        RowBuilder b = new RowBuilder(this);
+        c.accept(b);
+        mImpl.addSummaryRow((TemplateBuilderImpl) b.mImpl);
+        mHasSummary = true;
+        return this;
+    }
+
+    /**
+     * Sets the color to tint items displayed by this template (e.g. icons).
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @NonNull
+    public ListBuilder setColor(int color) {
+        mImpl.setColor(color);
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    @Override
+    protected TemplateBuilderImpl selectImpl() {
+        if (checkCompatible(SliceSpecs.LIST)) {
+            return new ListBuilderV1Impl(getBuilder(), SliceSpecs.LIST);
+        } else if (checkCompatible(SliceSpecs.BASIC)) {
+            return new ListBuilderBasicImpl(getBuilder(), SliceSpecs.BASIC);
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public androidx.app.slice.builders.impl.ListBuilder getImpl() {
+        return mImpl;
+    }
+
+    /**
+     * Sub-builder to construct a row of slice content.
+     * <p>
+     * Row content can have:
+     * <ul>
+     *     <li>Title item - This is only displayed if this is a list item in a large template, it
+     *     will not be shown if this template is being used for small format. The item appears at
+     *     the start of the template. There can only be one title item displayed, and it could be a
+     *     timestamp, image, or a tappable icon.</li>
+     *     <li>Title - Formatted as a title.</li>
+     *     <li>Subtitle - Appears below the title (if one exists) and is formatted as normal text.
+     *     </li>
+     *     <li>End item -  Appears at the end of the template, there can be multiple end items but
+     *     they are only shown if there's enough space. End items can be a timestamp, image, or a
+     *     tappable icon.</li>
+     * </ul>
+     * </p>
+     *
+     * @see ListBuilder
+     */
+    public static class RowBuilder extends TemplateSliceBuilder {
+
+        private androidx.app.slice.builders.impl.ListBuilder.RowBuilder mImpl;
+
+        private boolean mHasEndActionOrToggle;
+        private boolean mHasEndImage;
+        private boolean mHasDefaultToggle;
+        private boolean mHasTimestamp;
+
+        /**
+         * Create a builder which will construct a slice displayed in a row format.
+         * @param parent The builder constructing the parent slice.
+         */
+        public RowBuilder(@NonNull ListBuilder parent) {
+            super(parent.mImpl.createRowBuilder());
+        }
+
+        /**
+         */
+        public RowBuilder(@NonNull Uri uri) {
+            super(uri);
+            throw new RuntimeException("Stub, to be removed");
+        }
+
+        /**
+         * Create a builder which will construct a slice displayed in a row format.
+         * @param uri Uri to tag for this slice.
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public RowBuilder(@NonNull ListBuilder parent, @NonNull Uri uri) {
+            super(parent.mImpl.createRowBuilder(uri));
+        }
+
+        /**
+         * Create a builder which will construct a slice displayed in a row format.
+         * @param uri Uri to tag for this slice.
+         * @hide
+         */
+        @RestrictTo(LIBRARY)
+        public RowBuilder(@NonNull Context context, @NonNull Uri uri) {
+            super(new ListBuilder(context, uri).mImpl.createRowBuilder(uri));
+        }
+
+        /**
+         * Sets this row to be the header of the slice. This item will be displayed at the top of
+         * the slice and other items in the slice will scroll below it.
+         */
+        @NonNull
+        public RowBuilder setIsHeader(boolean isHeader) {
+            mImpl.setIsHeader(isHeader);
+            return this;
+        }
+
+        /**
+         * Sets the title item to be the provided timestamp. Only one timestamp can be added, if
+         * one is already added this will throw {@link IllegalArgumentException}.
+         * <p>
+         * There can only be one title item, this will replace any other title
+         * items that may have been set.
+         */
+        @NonNull
+        public RowBuilder setTitleItem(long timeStamp) {
+            if (mHasTimestamp) {
+                throw new IllegalArgumentException("Trying to add a timestamp when one has "
+                        + "already been added");
+            }
+            mImpl.setTitleItem(timeStamp);
+            mHasTimestamp = true;
+            return this;
+        }
+
+        /**
+         * Sets the title item to be the provided icon.
+         * <p>
+         * There can only be one title item, this will replace any other title
+         * items that may have been set.
+         */
+        @NonNull
+        public RowBuilder setTitleItem(@NonNull Icon icon) {
+            mImpl.setTitleItem(icon);
+            return this;
+        }
+
+        /**
+         * Sets the title item to be a tappable icon.
+         * <p>
+         * There can only be one title item, this will replace any other title
+         * items that may have been set.
+         */
+        @NonNull
+        public RowBuilder setTitleItem(@NonNull Icon icon, @NonNull PendingIntent action) {
+            mImpl.setTitleItem(icon, action);
+            return this;
+        }
+
+        /**
+         * Sets the action to be invoked if the user taps on the main content of the template.
+         */
+        @NonNull
+        public RowBuilder setContentIntent(@NonNull PendingIntent action) {
+            mImpl.setContentIntent(action);
+            return this;
+        }
+
+        /**
+         * Sets the title text.
+         */
+        @NonNull
+        public RowBuilder setTitle(CharSequence title) {
+            mImpl.setTitle(title);
+            return this;
+        }
+
+        /**
+         * Sets the subtitle text.
+         */
+        @NonNull
+        public RowBuilder setSubtitle(CharSequence subtitle) {
+            mImpl.setSubtitle(subtitle);
+            return this;
+        }
+
+        /**
+         * Adds a timestamp to be displayed at the end of the row. Only one timestamp can be added,
+         * if one is already added this will throw {@link IllegalArgumentException}.
+         */
+        @NonNull
+        public RowBuilder addEndItem(long timeStamp) {
+            if (mHasTimestamp) {
+                throw new IllegalArgumentException("Trying to add a timestamp when one has "
+                        + "already been added");
+            }
+            mImpl.addEndItem(timeStamp);
+            mHasTimestamp = true;
+            return this;
+        }
+
+        /**
+         * Adds an icon to be displayed at the end of the row. A mixture of icons and tappable
+         * icons is not permitted. If an action has already been added this will throw
+         * {@link IllegalArgumentException}.
+         */
+        @NonNull
+        public RowBuilder addEndItem(@NonNull Icon icon) {
+            if (mHasEndActionOrToggle) {
+                throw new IllegalArgumentException("Trying to add an icon to end items when an"
+                        + "action has already been added. End items cannot have a mixture of "
+                        + "tappable icons and icons.");
+            }
+            mImpl.addEndItem(icon);
+            mHasEndImage = true;
+            return this;
+        }
+
+        /**
+         * Adds a tappable icon to be displayed at the end of the row. A mixture of icons and
+         * tappable icons is not permitted. If an icon has already been added, this will throw
+         * {@link IllegalArgumentException}.
+         */
+        @NonNull
+        public RowBuilder addEndItem(@NonNull Icon icon, @NonNull PendingIntent action) {
+            if (mHasEndImage) {
+                throw new IllegalArgumentException("Trying to add an action to end items when an"
+                        + "icon has already been added. End items cannot have a mixture of "
+                        + "tappable icons and icons.");
+            }
+            mImpl.addEndItem(icon, action);
+            mHasEndActionOrToggle = true;
+            return this;
+        }
+
+        /**
+         * Adds a toggle action to be displayed at the end of the row. A mixture of icons and
+         * tappable icons is not permitted. If an icon has already been added, this will throw an
+         * {@link IllegalArgumentException}.
+         */
+        @NonNull
+        public RowBuilder addToggle(@NonNull PendingIntent action, boolean isChecked) {
+            return addToggleInternal(action, isChecked, null);
+        }
+
+        /**
+         * Adds a toggle action to be displayed with custom icons to represent checked and
+         * unchecked state at the end of the row. A mixture of icons and tappable icons is not
+         * permitted. If an icon has already been added, this will throw an
+         * {@link IllegalArgumentException}.
+         */
+        @NonNull
+        public RowBuilder addToggle(@NonNull PendingIntent action, boolean isChecked,
+                @NonNull Icon icon) {
+            return addToggleInternal(action, isChecked, icon);
+        }
+
+        private RowBuilder addToggleInternal(@NonNull PendingIntent action, boolean isChecked,
+                @Nullable Icon icon) {
+            if (mHasEndImage) {
+                throw new IllegalStateException("Trying to add a toggle to end items when an "
+                        + "icon has already been added. End items cannot have a mixture of "
+                        + "tappable icons and icons.");
+            }
+            if (mHasDefaultToggle) {
+                throw new IllegalStateException("Only one non-custom toggle can be added "
+                        + "in a single row. If you would like to include multiple toggles "
+                        + "in a row, set a custom icon for each toggle.");
+            }
+            mImpl.addToggle(action, isChecked, icon);
+            mHasDefaultToggle = icon == null;
+            mHasEndActionOrToggle = true;
+            return this;
+        }
+
+        @Override
+        void setImpl(TemplateBuilderImpl impl) {
+            mImpl = (androidx.app.slice.builders.impl.ListBuilder.RowBuilder) impl;
+        }
+
+        /**
+         */
+        public void apply(Slice.Builder builder) {
+            throw new RuntimeException("Stub, to be removed");
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/MessagingSliceBuilder.java b/slices/builders/src/main/java/androidx/app/slice/builders/MessagingSliceBuilder.java
new file mode 100644
index 0000000..434ab75
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/MessagingSliceBuilder.java
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.builders;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+
+import java.util.function.Consumer;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpecs;
+import androidx.app.slice.builders.impl.MessagingBasicImpl;
+import androidx.app.slice.builders.impl.MessagingBuilder;
+import androidx.app.slice.builders.impl.MessagingListV1Impl;
+import androidx.app.slice.builders.impl.MessagingV1Impl;
+import androidx.app.slice.builders.impl.TemplateBuilderImpl;
+
+/**
+ * Builder to construct slice content in a messaging format.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class MessagingSliceBuilder extends TemplateSliceBuilder {
+
+    /**
+     * The maximum number of messages that will be retained in the Slice itself (the
+     * number displayed is up to the platform).
+     */
+    public static final int MAXIMUM_RETAINED_MESSAGES = 50;
+
+    private MessagingBuilder mBuilder;
+
+    /**
+     */
+    public MessagingSliceBuilder(@NonNull Uri uri) {
+        super(uri);
+        throw new RuntimeException("Stub, to be removed");
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public MessagingSliceBuilder(@NonNull Context context, @NonNull Uri uri) {
+        super(context, uri);
+    }
+
+    /**
+     * Add a subslice to this builder.
+     */
+    public MessagingSliceBuilder add(MessageBuilder builder) {
+        mBuilder.add((TemplateBuilderImpl) builder.mImpl);
+        return this;
+    }
+
+    /**
+     * Add a subslice to this builder.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    public MessagingSliceBuilder add(Consumer<MessageBuilder> c) {
+        MessageBuilder b = new MessageBuilder(this);
+        c.accept(b);
+        return add(b);
+    }
+
+    @Override
+    void setImpl(TemplateBuilderImpl impl) {
+        mBuilder = (MessagingBuilder) impl;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    @Override
+    protected TemplateBuilderImpl selectImpl() {
+        if (checkCompatible(SliceSpecs.MESSAGING)) {
+            return new MessagingV1Impl(getBuilder(), SliceSpecs.MESSAGING);
+        } else if (checkCompatible(SliceSpecs.LIST)) {
+            return new MessagingListV1Impl(getBuilder(), SliceSpecs.LIST);
+        } else if (checkCompatible(SliceSpecs.BASIC)) {
+            return new MessagingBasicImpl(getBuilder(), SliceSpecs.BASIC);
+        }
+        return null;
+    }
+
+    /**
+     * Builder for adding a message to {@link MessagingSliceBuilder}.
+     */
+    public static final class MessageBuilder extends TemplateSliceBuilder {
+
+        private MessagingBuilder.MessageBuilder mImpl;
+
+        /**
+         * Creates a MessageBuilder with the specified parent.
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public MessageBuilder(MessagingSliceBuilder parent) {
+            super(parent.mBuilder.createMessageBuilder());
+        }
+
+        /**
+         * Add the icon used to display contact in the messaging experience
+         */
+        public MessageBuilder addSource(Icon source) {
+            mImpl.addSource(source);
+            return this;
+        }
+
+        /**
+         * Add the text to be used for this message.
+         */
+        public MessageBuilder addText(CharSequence text) {
+            mImpl.addText(text);
+            return this;
+        }
+
+        /**
+         * Add the time at which this message arrived in ms since Unix epoch
+         */
+        public MessageBuilder addTimestamp(long timestamp) {
+            mImpl.addTimestamp(timestamp);
+            return this;
+        }
+
+        @Override
+        void setImpl(TemplateBuilderImpl impl) {
+            mImpl = (MessagingBuilder.MessageBuilder) impl;
+        }
+
+        /**
+         */
+        public void apply(Slice.Builder builder) {
+            throw new RuntimeException("Stub, to be removed");
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/TemplateSliceBuilder.java b/slices/builders/src/main/java/androidx/app/slice/builders/TemplateSliceBuilder.java
new file mode 100644
index 0000000..dfbd055
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/TemplateSliceBuilder.java
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.builders;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.RestrictTo;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.SliceSpec;
+import androidx.app.slice.SliceSpecs;
+import androidx.app.slice.builders.impl.TemplateBuilderImpl;
+
+/**
+ * Base class of builders of various template types.
+ */
+public abstract class TemplateSliceBuilder {
+
+    private static final String TAG = "TemplateSliceBuilder";
+
+    private final Slice.Builder mBuilder;
+    private final Context mContext;
+    private final TemplateBuilderImpl mImpl;
+    private List<SliceSpec> mSpecs;
+
+    /**
+     */
+    public TemplateSliceBuilder(Uri uri) {
+        this(null, uri);
+        throw new RuntimeException("Stub, to be removed");
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    protected TemplateSliceBuilder(TemplateBuilderImpl impl) {
+        mContext = null;
+        mBuilder = null;
+        mImpl = impl;
+        setImpl(impl);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    protected TemplateSliceBuilder(Slice.Builder b, Context context) {
+        mBuilder = b;
+        mContext = context;
+        mSpecs = getSpecs();
+        mImpl = selectImpl();
+        if (mImpl == null) {
+            throw new IllegalArgumentException("No valid specs found");
+        }
+        setImpl(mImpl);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public TemplateSliceBuilder(Context context, Uri uri) {
+        mBuilder = new Slice.Builder(uri);
+        mContext = context;
+        mSpecs = getSpecs();
+        mImpl = selectImpl();
+        if (mImpl == null) {
+            throw new IllegalArgumentException("No valid specs found");
+        }
+        setImpl(mImpl);
+    }
+
+    /**
+     * Construct the slice.
+     */
+    public Slice build() {
+        return mImpl.build();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    protected Slice.Builder getBuilder() {
+        return mBuilder;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    abstract void setImpl(TemplateBuilderImpl impl);
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    protected TemplateBuilderImpl selectImpl() {
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    protected boolean checkCompatible(SliceSpec candidate) {
+        final int size = mSpecs.size();
+        for (int i = 0; i < size; i++) {
+            if (mSpecs.get(i).canRender(candidate)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private List<SliceSpec> getSpecs() {
+        if (SliceProvider.getCurrentSpecs() != null) {
+            return SliceProvider.getCurrentSpecs();
+        }
+        // TODO: Support getting specs from pinned info.
+        Log.w(TAG, "Not currently bunding a slice");
+        return Arrays.asList(SliceSpecs.BASIC);
+    }
+
+    /**
+     * This is for typing, to clean up the code.
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    static <T> Pair<SliceSpec, Class<? extends TemplateBuilderImpl>> pair(SliceSpec spec,
+            Class<T> cls) {
+        return new Pair(spec, cls);
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/GridBuilder.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/GridBuilder.java
new file mode 100644
index 0000000..28134be
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/GridBuilder.java
@@ -0,0 +1,96 @@
+/*
+ * 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.app.slice.builders.impl;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import androidx.app.slice.Slice;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public interface GridBuilder {
+    /**
+     * Create an TemplateBuilderImpl that implements {@link CellBuilder}.
+     */
+    TemplateBuilderImpl createGridBuilder();
+
+    /**
+     * Create an TemplateBuilderImpl that implements {@link CellBuilder} with the specified Uri.
+     */
+    TemplateBuilderImpl createGridBuilder(Uri uri);
+
+    /**
+     * Add a cell to this builder. Expected to be a builder from {@link #createGridBuilder};
+     */
+    void addCell(TemplateBuilderImpl impl);
+
+    /**
+     * Builds a standalone slice of this grid builder (i.e. not contained within a List).
+     */
+    Slice buildIndividual();
+
+    /**
+     */
+    interface CellBuilder {
+        /**
+         * Adds text to the cell. There can be at most two text items, the first two added
+         * will be used, others will be ignored.
+         */
+        @NonNull
+        void addText(@NonNull CharSequence text);
+
+        /**
+         * Adds text to the cell. Text added with this method will be styled as a title.
+         * There can be at most two text items, the first two added will be used, others
+         * will be ignored.
+         */
+        @NonNull
+        void addTitleText(@NonNull CharSequence text);
+
+        /**
+         * Adds an image to the cell that should be displayed as large as the cell allows.
+         * There can be at most one image, the first one added will be used, others will be ignored.
+         *
+         * @param image the image to display in the cell.
+         */
+        @NonNull
+        void addLargeImage(@NonNull Icon image);
+
+        /**
+         * Adds an image to the cell. There can be at most one image, the first one added
+         * will be used, others will be ignored.
+         *
+         * @param image the image to display in the cell.
+         */
+        @NonNull
+        void addImage(@NonNull Icon image);
+
+        /**
+         * Sets the action to be invoked if the user taps on this cell in the row.
+         */
+        @NonNull
+        void setContentIntent(@NonNull PendingIntent intent);
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/GridBuilderBasicImpl.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/GridBuilderBasicImpl.java
new file mode 100644
index 0000000..8205013
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/GridBuilderBasicImpl.java
@@ -0,0 +1,138 @@
+/*
+ * 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.app.slice.builders.impl;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpec;
+
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class GridBuilderBasicImpl extends TemplateBuilderImpl implements GridBuilder {
+
+    /**
+     */
+    public GridBuilderBasicImpl(Slice.Builder b, SliceSpec spec) {
+        super(b, spec);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridBuilder() {
+        return new CellBuilder(this);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridBuilder(Uri uri) {
+        return new CellBuilder(uri);
+    }
+
+    /**
+     */
+    @Override
+    public void addCell(TemplateBuilderImpl impl) {
+        // TODO: Consider extracting some grid content for the basic version.
+    }
+
+    /**
+     */
+    @Override
+    public Slice buildIndividual() {
+        // Empty slice, nothing useful from a grid to basic.
+        return getBuilder().build();
+    }
+
+    /**
+     */
+    @Override
+    public void apply(Slice.Builder builder) {
+
+    }
+
+    /**
+     */
+    public static final class CellBuilder extends TemplateBuilderImpl implements
+            GridBuilder.CellBuilder {
+
+        /**
+         */
+        public CellBuilder(@NonNull GridBuilderBasicImpl parent) {
+            super(parent.createChildBuilder(), null);
+        }
+
+        /**
+         */
+        public CellBuilder(@NonNull Uri uri) {
+            super(new Slice.Builder(uri), null);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addText(@NonNull CharSequence text) {
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addTitleText(@NonNull CharSequence text) {
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addLargeImage(@NonNull Icon image) {
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addImage(@NonNull Icon image) {
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setContentIntent(@NonNull PendingIntent intent) {
+        }
+
+        /**
+         */
+        @Override
+        public void apply(Slice.Builder builder) {
+
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/GridBuilderListV1Impl.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/GridBuilderListV1Impl.java
new file mode 100644
index 0000000..0ca01e6
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/GridBuilderListV1Impl.java
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.builders.impl;
+
+import static android.app.slice.Slice.HINT_HORIZONTAL;
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpec;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class GridBuilderListV1Impl extends TemplateBuilderImpl implements GridBuilder {
+
+    /**
+     */
+    public GridBuilderListV1Impl(@NonNull Slice.Builder builder, SliceSpec spec) {
+        super(builder, spec);
+    }
+
+    /**
+     */
+    @Override
+    public void apply(Slice.Builder builder) {
+
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridBuilder() {
+        return new CellBuilder(this);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridBuilder(Uri uri) {
+        return new CellBuilder(uri);
+    }
+
+    /**
+     */
+    @Override
+    public void addCell(TemplateBuilderImpl builder) {
+        getBuilder().addSubSlice(builder.build());
+    }
+
+    /**
+     */
+    @Override
+    public Slice buildIndividual() {
+        return new Slice.Builder(getBuilder()).addHints(HINT_HORIZONTAL, HINT_LIST_ITEM)
+                .addSubSlice(getBuilder()
+                        .addHints(HINT_HORIZONTAL, HINT_LIST_ITEM).build()).build();
+    }
+
+    /**
+     */
+    public static final class CellBuilder extends TemplateBuilderImpl implements
+            GridBuilder.CellBuilder {
+
+        private PendingIntent mContentIntent;
+
+        /**
+         */
+        public CellBuilder(@NonNull GridBuilderListV1Impl parent) {
+            super(parent.createChildBuilder(), null);
+        }
+
+        /**
+         */
+        public CellBuilder(@NonNull Uri uri) {
+            super(new Slice.Builder(uri), null);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addText(@NonNull CharSequence text) {
+            getBuilder().addText(text, null);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addTitleText(@NonNull CharSequence text) {
+            getBuilder().addText(text, null, HINT_LARGE);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addLargeImage(@NonNull Icon image) {
+            getBuilder().addIcon(image, null, HINT_LARGE);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addImage(@NonNull Icon image) {
+            getBuilder().addIcon(image, null);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setContentIntent(@NonNull PendingIntent intent) {
+            mContentIntent = intent;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY)
+        @Override
+        public void apply(Slice.Builder b) {
+        }
+
+        /**
+         */
+        @Override
+        @NonNull
+        public Slice build() {
+            if (mContentIntent != null) {
+                return new Slice.Builder(getBuilder())
+                        .addHints(HINT_HORIZONTAL, HINT_LIST_ITEM)
+                        .addAction(mContentIntent, getBuilder().build(), null)
+                        .build();
+            }
+            return getBuilder().addHints(HINT_HORIZONTAL, HINT_LIST_ITEM).build();
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilder.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilder.java
new file mode 100644
index 0000000..d9605b4
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilder.java
@@ -0,0 +1,136 @@
+/*
+ * 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.app.slice.builders.impl;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public interface ListBuilder {
+
+    /**
+     * Add a row to list builder.
+     */
+    void addRow(TemplateBuilderImpl impl);
+    /**
+     * Add a grid row to the list builder.
+     */
+    void addGrid(TemplateBuilderImpl impl);
+    /**
+     * Add a summary row for this template. The summary content is displayed
+     * when the slice is displayed in small format.
+     */
+    void addSummaryRow(TemplateBuilderImpl builder);
+
+    /**
+     * Sets the color to tint items displayed by this template (e.g. icons).
+     */
+    void setColor(int color);
+
+    /**
+     * Create a builder that implements {@link RowBuilder}.
+     */
+    TemplateBuilderImpl createRowBuilder();
+    /**
+     * Create a builder that implements {@link RowBuilder}.
+     */
+    TemplateBuilderImpl createRowBuilder(Uri uri);
+
+    /**
+     * Create a builder that implements {@link GridBuilder}.
+     */
+    TemplateBuilderImpl createGridBuilder();
+
+    /**
+     */
+    public interface RowBuilder {
+
+        /**
+         * Sets this row to be the header of the slice. This item will be displayed at the top of
+         * the slice and other items in the slice will scroll below it.
+         */
+        void setIsHeader(boolean isHeader);
+
+        /**
+         * Sets the title item to be the provided timestamp. Only one timestamp can be added, if
+         * one is already added this will throw {@link IllegalArgumentException}.
+         * <p>
+         * There can only be one title item, this will replace any other title
+         * items that may have been set.
+         */
+        void setTitleItem(long timeStamp);
+
+        /**
+         * Sets the title item to be the provided icon.
+         * <p>
+         * There can only be one title item, this will replace any other title
+         * items that may have been set.
+         */
+        void setTitleItem(Icon icon);
+
+        /**
+         * Sets the title item to be a tappable icon.
+         * <p>
+         * There can only be one title item, this will replace any other title
+         * items that may have been set.
+         */
+        void setTitleItem(Icon icon, PendingIntent action);
+
+        /**
+         * Sets the action to be invoked if the user taps on the main content of the template.
+         */
+        void setContentIntent(PendingIntent action);
+
+        /**
+         * Sets the title text.
+         */
+        void setTitle(CharSequence title);
+
+        /**
+         * Sets the subtitle text.
+         */
+        void setSubtitle(CharSequence subtitle);
+
+        /**
+         * Adds a timestamp to be displayed at the end of the row.
+         */
+        void addEndItem(long timeStamp);
+
+        /**
+         * Adds an icon to be displayed at the end of the row.
+         */
+        void addEndItem(Icon icon);
+
+        /**
+         * Adds a tappable icon to be displayed at the end of the row.
+         */
+        void addEndItem(Icon icon, PendingIntent action);
+
+        /**
+         * Adds a toggle action to the template with custom icons to represent checked and unchecked
+         * state.
+         */
+        void addToggle(PendingIntent action, boolean isChecked, Icon icon);
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilderBasicImpl.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilderBasicImpl.java
new file mode 100644
index 0000000..f9e4ac6
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilderBasicImpl.java
@@ -0,0 +1,212 @@
+/*
+ * 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.app.slice.builders.impl;
+
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpec;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ListBuilderBasicImpl extends TemplateBuilderImpl implements ListBuilder {
+
+    /**
+     */
+    public ListBuilderBasicImpl(Slice.Builder b, SliceSpec spec) {
+        super(b, spec);
+    }
+
+    /**
+     */
+    @Override
+    public void addRow(TemplateBuilderImpl impl) {
+        // Do nothing.
+    }
+
+    /**
+     */
+    @Override
+    public void addGrid(TemplateBuilderImpl impl) {
+        // Do nothing.
+    }
+
+    /**
+     */
+    @Override
+    public void addSummaryRow(TemplateBuilderImpl builder) {
+        RowBuilderImpl row = (RowBuilderImpl) builder;
+        if (row.mIcon != null) {
+            getBuilder().addIcon(row.mIcon, null);
+        }
+        if (row.mTitle != null) {
+            getBuilder().addText(row.mTitle, null, android.app.slice.Slice.HINT_TITLE);
+        }
+        if (row.mSubtitle != null) {
+            getBuilder().addText(row.mSubtitle, null);
+        }
+    }
+
+    /**
+     */
+    @Override
+    public void setColor(int color) {
+
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createRowBuilder() {
+        return new RowBuilderImpl(this);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createRowBuilder(Uri uri) {
+        return new RowBuilderImpl(uri);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridBuilder() {
+        return new GridBuilderBasicImpl(createChildBuilder(), null);
+    }
+
+    /**
+     */
+    @Override
+    public void apply(Slice.Builder builder) {
+
+    }
+
+    /**
+     */
+    public static class RowBuilderImpl extends TemplateBuilderImpl
+            implements ListBuilder.RowBuilder {
+        private Icon mIcon;
+        private CharSequence mTitle;
+        private CharSequence mSubtitle;
+
+        /**
+         */
+        public RowBuilderImpl(@NonNull ListBuilderBasicImpl parent) {
+            super(parent.createChildBuilder(), null);
+        }
+
+        /**
+         */
+        public RowBuilderImpl(@NonNull Uri uri) {
+            super(new Slice.Builder(uri), null);
+        }
+
+        /**
+         */
+        @Override
+        public void addEndItem(Icon icon) {
+
+        }
+
+        /**
+         */
+        @Override
+        public void addEndItem(Icon icon, PendingIntent action) {
+
+        }
+
+        /**
+         */
+        @Override
+        public void addToggle(PendingIntent action, boolean isChecked, Icon icon) {
+
+        }
+
+        /**
+         */
+        @Override
+        public void setIsHeader(boolean isHeader) {
+
+        }
+
+        /**
+         */
+        @Override
+        public void setTitleItem(long timeStamp) {
+
+        }
+
+        /**
+         */
+        @Override
+        public void setTitleItem(Icon icon) {
+            mIcon = icon;
+        }
+
+        /**
+         */
+        @Override
+        public void setTitleItem(Icon icon, PendingIntent action) {
+            mIcon = icon;
+        }
+
+        /**
+         */
+        @Override
+        public void setContentIntent(PendingIntent action) {
+
+        }
+
+        /**
+         */
+        @Override
+        public void setTitle(CharSequence title) {
+            mTitle = title;
+        }
+
+        /**
+         */
+        @Override
+        public void setSubtitle(CharSequence subtitle) {
+            mSubtitle = subtitle;
+        }
+
+        /**
+         */
+        @Override
+        public void addEndItem(long timeStamp) {
+
+        }
+
+        /**
+         */
+        @Override
+        public void apply(Slice.Builder builder) {
+
+        }
+    }
+}
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
new file mode 100644
index 0000000..15a17c6
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/ListBuilderV1Impl.java
@@ -0,0 +1,276 @@
+/*
+ * 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.app.slice.builders.impl;
+
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_SELECTED;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.Slice.SUBTYPE_COLOR;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import static androidx.app.slice.core.SliceHints.HINT_SUMMARY;
+import static androidx.app.slice.core.SliceHints.SUBTYPE_TOGGLE;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import java.util.ArrayList;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.SliceSpec;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ListBuilderV1Impl extends TemplateBuilderImpl implements ListBuilder {
+
+    /**
+     */
+    public ListBuilderV1Impl(Slice.Builder b, SliceSpec spec) {
+        super(b, spec);
+    }
+
+    /**
+     */
+    @Override
+    public void apply(Slice.Builder builder) {
+
+    }
+
+    /**
+     * Add a row to list builder.
+     */
+    @NonNull
+    @Override
+    public void addRow(@NonNull TemplateBuilderImpl builder) {
+        getBuilder().addSubSlice(builder.build());
+    }
+
+    /**
+     */
+    @NonNull
+    @Override
+    public void addGrid(@NonNull TemplateBuilderImpl builder) {
+        getBuilder().addSubSlice(builder.build());
+    }
+
+    /**
+     */
+    @Override
+    public void addSummaryRow(TemplateBuilderImpl builder) {
+        builder.getBuilder().addHints(HINT_SUMMARY);
+        getBuilder().addSubSlice(builder.build(), null);
+    }
+
+    /**
+     */
+    @NonNull
+    @Override
+    public void setColor(int color) {
+        getBuilder().addInt(color, SUBTYPE_COLOR);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createRowBuilder() {
+        return new RowBuilderImpl(this);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createRowBuilder(Uri uri) {
+        return new RowBuilderImpl(uri);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridBuilder() {
+        return new GridBuilderListV1Impl(createChildBuilder(), null);
+    }
+
+    /**
+     */
+    public static class RowBuilderImpl extends TemplateBuilderImpl
+            implements ListBuilder.RowBuilder {
+
+        private boolean mIsHeader;
+        private PendingIntent mContentIntent;
+        private SliceItem mTitleItem;
+        private SliceItem mSubtitleItem;
+        private SliceItem mStartItem;
+        private ArrayList<SliceItem> mEndItems = new ArrayList<>();
+
+        /**
+         */
+        public RowBuilderImpl(@NonNull ListBuilderV1Impl parent) {
+            super(parent.createChildBuilder(), null);
+        }
+
+        /**
+         */
+        public RowBuilderImpl(@NonNull Uri uri) {
+            super(new Slice.Builder(uri), null);
+        }
+
+        /**
+         */
+        public RowBuilderImpl(Slice.Builder builder) {
+            super(builder, null);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setIsHeader(boolean isHeader) {
+            mIsHeader = isHeader;
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setTitleItem(long timeStamp) {
+            mStartItem = new SliceItem(timeStamp, FORMAT_TIMESTAMP, null, new String[0]);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setTitleItem(@NonNull Icon icon) {
+            mStartItem = new SliceItem(icon, FORMAT_IMAGE, null, new String[0]);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setTitleItem(@NonNull Icon icon, @NonNull PendingIntent action) {
+            Slice actionSlice = new Slice.Builder(getBuilder()).addIcon(icon, null).build();
+            mStartItem = new SliceItem(action, actionSlice, FORMAT_ACTION, null, new String[0]);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setContentIntent(@NonNull PendingIntent action) {
+            mContentIntent = action;
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setTitle(CharSequence title) {
+            mTitleItem = new SliceItem(title, FORMAT_TEXT, null, new String[]{HINT_TITLE});
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setSubtitle(CharSequence subtitle) {
+            mSubtitleItem = new SliceItem(subtitle, FORMAT_TEXT, null, new String[0]);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addEndItem(long timeStamp) {
+            mEndItems.add(new SliceItem(timeStamp, FORMAT_TIMESTAMP, null, new String[0]));
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addEndItem(@NonNull Icon icon) {
+            mEndItems.add(new SliceItem(icon, FORMAT_IMAGE, null,
+                    new String[]{HINT_NO_TINT, HINT_LARGE}));
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addEndItem(@NonNull Icon icon, @NonNull PendingIntent action) {
+            Slice actionSlice = new Slice.Builder(getBuilder()).addIcon(icon, null).build();
+            mEndItems.add(new SliceItem(action, actionSlice, FORMAT_ACTION, null, new String[0]));
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addToggle(@NonNull PendingIntent action, boolean isChecked,
+                @NonNull Icon icon) {
+            @Slice.SliceHint String[] hints = isChecked
+                    ? new String[] {SUBTYPE_TOGGLE, HINT_SELECTED}
+                    : new String[] {SUBTYPE_TOGGLE};
+            Slice.Builder actionSliceBuilder = new Slice.Builder(getBuilder()).addHints(hints);
+            if (icon != null) {
+                actionSliceBuilder.addIcon(icon, null);
+            }
+            Slice actionSlice = actionSliceBuilder.build();
+            mEndItems.add(new SliceItem(action, actionSlice, FORMAT_ACTION, null, hints));
+        }
+
+        /**
+         */
+        @Override
+        public void apply(Slice.Builder b) {
+            Slice.Builder wrapped = b;
+            if (mContentIntent != null) {
+                b = new Slice.Builder(wrapped);
+            }
+            if (mStartItem != null) {
+                b.addItem(mStartItem);
+            }
+            if (mTitleItem != null) {
+                b.addItem(mTitleItem);
+            }
+            if (mSubtitleItem != null) {
+                b.addItem(mSubtitleItem);
+            }
+            for (int i = 0; i < mEndItems.size(); i++) {
+                SliceItem item = mEndItems.get(i);
+                b.addItem(item);
+            }
+            if (mContentIntent != null) {
+                wrapped.addAction(mContentIntent, b.build(), null);
+            }
+            wrapped.addHints(mIsHeader ? null : HINT_LIST_ITEM);
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingBasicImpl.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingBasicImpl.java
new file mode 100644
index 0000000..843302c
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingBasicImpl.java
@@ -0,0 +1,120 @@
+/*
+ * 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.app.slice.builders.impl;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.graphics.drawable.Icon;
+import android.support.annotation.RestrictTo;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpec;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class MessagingBasicImpl extends TemplateBuilderImpl implements
+        MessagingBuilder {
+    private MessageBuilder mLastMessage;
+
+    /**
+     */
+    public MessagingBasicImpl(Slice.Builder builder, SliceSpec spec) {
+        super(builder, spec);
+    }
+
+    /**
+     */
+    @Override
+    public void apply(Slice.Builder builder) {
+        if (mLastMessage != null) {
+            if (mLastMessage.mIcon != null) {
+                builder.addIcon(mLastMessage.mIcon, null);
+            }
+            if (mLastMessage.mText != null) {
+                builder.addText(mLastMessage.mText, null);
+            }
+        }
+    }
+
+    /**
+     */
+    @Override
+    public void add(TemplateBuilderImpl builder) {
+        MessageBuilder b = (MessageBuilder) builder;
+        if (mLastMessage == null || mLastMessage.mTimestamp < b.mTimestamp) {
+            mLastMessage = b;
+        }
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createMessageBuilder() {
+        return new MessageBuilder(this);
+    }
+
+    /**
+     */
+    public static final class MessageBuilder extends TemplateBuilderImpl
+            implements MessagingBuilder.MessageBuilder {
+
+        private Icon mIcon;
+        private CharSequence mText;
+        private long mTimestamp;
+
+        /**
+         */
+        public MessageBuilder(MessagingBasicImpl parent) {
+            this(parent.createChildBuilder());
+        }
+
+        /**
+         */
+        private MessageBuilder(Slice.Builder builder) {
+            super(builder, null);
+        }
+
+        /**
+         */
+        @Override
+        public void addSource(Icon source) {
+            mIcon = source;
+        }
+
+        /**
+         */
+        @Override
+        public void addText(CharSequence text) {
+            mText = text;
+        }
+
+        /**
+         */
+        @Override
+        public void addTimestamp(long timestamp) {
+            mTimestamp = timestamp;
+        }
+
+        /**
+         */
+        @Override
+        public void apply(Slice.Builder builder) {
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingBuilder.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingBuilder.java
new file mode 100644
index 0000000..635f160
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingBuilder.java
@@ -0,0 +1,58 @@
+/*
+ * 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.app.slice.builders.impl;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.graphics.drawable.Icon;
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public interface MessagingBuilder {
+    /**
+     * Add a subslice to this builder.
+     */
+    void add(TemplateBuilderImpl builder);
+
+    /**
+     * Create a builder that implements {@link MessageBuilder}
+     */
+    TemplateBuilderImpl createMessageBuilder();
+
+    /**
+     */
+    public interface MessageBuilder {
+
+        /**
+         * Add the icon used to display contact in the messaging experience
+         */
+        void addSource(Icon source);
+
+        /**
+         * Add the text to be used for this message.
+         */
+        void addText(CharSequence text);
+
+        /**
+         * Add the time at which this message arrived in ms since Unix epoch
+         */
+        void addTimestamp(long timestamp);
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingListV1Impl.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingListV1Impl.java
new file mode 100644
index 0000000..408ad0b
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingListV1Impl.java
@@ -0,0 +1,109 @@
+/*
+ * 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.app.slice.builders.impl;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.graphics.drawable.Icon;
+import android.support.annotation.RestrictTo;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpec;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class MessagingListV1Impl extends TemplateBuilderImpl implements MessagingBuilder{
+
+    private final ListBuilderV1Impl mListBuilder;
+
+    /**
+     */
+    public MessagingListV1Impl(Slice.Builder b, SliceSpec spec) {
+        super(b, spec);
+        mListBuilder = new ListBuilderV1Impl(b, spec);
+    }
+
+    /**
+     */
+    @Override
+    public void add(TemplateBuilderImpl builder) {
+        MessageBuilder b = (MessageBuilder) builder;
+        mListBuilder.addRow(b.mListBuilder);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createMessageBuilder() {
+        return new MessageBuilder(this);
+    }
+
+    /**
+     */
+    @Override
+    public void apply(Slice.Builder builder) {
+        mListBuilder.apply(builder);
+    }
+
+    /**
+     */
+    public static final class MessageBuilder extends TemplateBuilderImpl
+            implements MessagingBuilder.MessageBuilder {
+        private final ListBuilderV1Impl.RowBuilderImpl mListBuilder;
+
+        /**
+         */
+        public MessageBuilder(MessagingListV1Impl parent) {
+            this(parent.createChildBuilder());
+        }
+
+        private MessageBuilder(Slice.Builder builder) {
+            super(builder, null);
+            mListBuilder = new ListBuilderV1Impl.RowBuilderImpl(builder);
+        }
+
+        /**
+         */
+        @Override
+        public void addSource(Icon source) {
+            mListBuilder.setTitleItem(source);
+        }
+
+        /**
+         */
+        @Override
+        public void addText(CharSequence text) {
+            mListBuilder.setSubtitle(text);
+        }
+
+        /**
+         */
+        @Override
+        public void addTimestamp(long timestamp) {
+            mListBuilder.addEndItem(timestamp);
+        }
+
+        /**
+         */
+        @Override
+        public void apply(Slice.Builder builder) {
+            mListBuilder.apply(builder);
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingV1Impl.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingV1Impl.java
new file mode 100644
index 0000000..4e07139
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/MessagingV1Impl.java
@@ -0,0 +1,97 @@
+/*
+ * 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.app.slice.builders.impl;
+
+import static android.app.slice.Slice.SUBTYPE_MESSAGE;
+
+import android.graphics.drawable.Icon;
+import android.support.annotation.RestrictTo;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpec;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class MessagingV1Impl extends TemplateBuilderImpl implements MessagingBuilder {
+
+    /**
+     */
+    public MessagingV1Impl(Slice.Builder b, SliceSpec spec) {
+        super(b, spec);
+    }
+
+    /**
+     */
+    @Override
+    public void add(TemplateBuilderImpl builder) {
+        getBuilder().addSubSlice(builder.build(), SUBTYPE_MESSAGE);
+    }
+
+    /**
+     */
+    @Override
+    public void apply(Slice.Builder builder) {
+
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createMessageBuilder() {
+        return new MessageBuilder(this);
+    }
+
+    /**
+     */
+    public static final class MessageBuilder extends TemplateBuilderImpl
+            implements MessagingBuilder.MessageBuilder {
+        /**
+         */
+        public MessageBuilder(MessagingV1Impl parent) {
+            super(parent.createChildBuilder(), null);
+        }
+
+        /**
+         */
+        @Override
+        public void addSource(Icon source) {
+            getBuilder().addIcon(source, android.app.slice.Slice.SUBTYPE_SOURCE);
+        }
+
+        /**
+         */
+        @Override
+        public void addText(CharSequence text) {
+            getBuilder().addText(text, null);
+        }
+
+        /**
+         */
+        @Override
+        public void addTimestamp(long timestamp) {
+            getBuilder().addTimestamp(timestamp, null);
+        }
+
+        /**
+         */
+        @Override
+        public void apply(Slice.Builder builder) {
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/app/slice/builders/impl/TemplateBuilderImpl.java b/slices/builders/src/main/java/androidx/app/slice/builders/impl/TemplateBuilderImpl.java
new file mode 100644
index 0000000..294677e
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/app/slice/builders/impl/TemplateBuilderImpl.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.builders.impl;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.support.annotation.RestrictTo;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpec;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public abstract class TemplateBuilderImpl {
+
+    private final Slice.Builder mSliceBuilder;
+    private final SliceSpec mSpec;
+
+    protected TemplateBuilderImpl(Slice.Builder b, SliceSpec spec) {
+        mSliceBuilder = b;
+        mSpec = spec;
+    }
+
+    /**
+     * Construct the slice.
+     */
+    public Slice build() {
+        mSliceBuilder.setSpec(mSpec);
+        apply(mSliceBuilder);
+        return mSliceBuilder.build();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public Slice.Builder getBuilder() {
+        return mSliceBuilder;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public Slice.Builder createChildBuilder() {
+        return new Slice.Builder(mSliceBuilder);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public abstract void apply(Slice.Builder builder);
+}
diff --git a/slices/core/Android.mk b/slices/core/Android.mk
new file mode 100644
index 0000000..cd3a353
--- /dev/null
+++ b/slices/core/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-slices-core
+#
+# in their makefiles to include the resources and their dependencies in their package.
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := android-slices-core
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under,src/main/java) \
+    $(call all-Iaidl-files-under,src/main/java)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/src/main/res
+LOCAL_JAVA_LIBRARIES := \
+    android-support-compat \
+    android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/slices/core/api/current.txt b/slices/core/api/current.txt
new file mode 100644
index 0000000..834a60f
--- /dev/null
+++ b/slices/core/api/current.txt
@@ -0,0 +1,38 @@
+package androidx.app.slice {
+
+  public final class Slice {
+    method public static androidx.app.slice.Slice bindSlice(android.content.Context, android.net.Uri);
+    method public static androidx.app.slice.Slice bindSlice(android.content.Context, android.content.Intent);
+    method public java.util.List<java.lang.String> getHints();
+    method public java.util.List<androidx.app.slice.SliceItem> getItems();
+    method public android.net.Uri getUri();
+  }
+
+  public class SliceConvert {
+    ctor public SliceConvert();
+    method public static android.app.slice.Slice unwrap(androidx.app.slice.Slice);
+    method public static androidx.app.slice.Slice wrap(android.app.slice.Slice);
+  }
+
+  public class SliceItem {
+    method public android.app.PendingIntent getAction();
+    method public java.lang.String getFormat();
+    method public java.util.List<java.lang.String> getHints();
+    method public android.graphics.drawable.Icon getIcon();
+    method public int getInt();
+    method public androidx.app.slice.Slice getSlice();
+    method public java.lang.String getSubType();
+    method public java.lang.CharSequence getText();
+    method public long getTimestamp();
+    method public boolean hasHint(java.lang.String);
+  }
+
+  public abstract class SliceProvider extends android.content.ContentProvider {
+    ctor public SliceProvider();
+    method public abstract androidx.app.slice.Slice onBindSlice(android.net.Uri);
+    method public abstract boolean onCreateSliceProvider();
+    method public android.net.Uri onMapIntentToUri(android.content.Intent);
+  }
+
+}
+
diff --git a/slices/core/build.gradle b/slices/core/build.gradle
new file mode 100644
index 0000000..9cb7ba2
--- /dev/null
+++ b/slices/core/build.gradle
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    implementation project(":support-annotations")
+    implementation project(":appcompat-v7")
+
+    androidTestImplementation(TEST_RUNNER)
+}
+
+android {
+    sourceSets {
+        main.res.srcDirs = [
+                'res',
+                'res-public'
+        ]
+    }
+}
+
+supportLibrary {
+    name = "Common utilities for slices"
+    publish = true
+    mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+    mavenGroup = LibraryGroups.SLICES
+    inceptionYear = "2017"
+    description = "The slices core library provides utilities for the slices view and provider libraries"
+    minSdkVersion = 24
+}
diff --git a/slices/core/lint-baseline.xml b/slices/core/lint-baseline.xml
new file mode 100644
index 0000000..2cadde1
--- /dev/null
+++ b/slices/core/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/slices/core/src/androidTest/AndroidManifest.xml b/slices/core/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..fff3da4
--- /dev/null
+++ b/slices/core/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.app.slice.core.test">
+
+    <application>
+        <provider android:name="androidx.app.slice.SliceTestProvider"
+            android:authorities="androidx.app.slice.core.test"
+            android:process=":provider"
+            android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/slices/core/src/androidTest/NO_DOCS b/slices/core/src/androidTest/NO_DOCS
new file mode 100644
index 0000000..092a39c
--- /dev/null
+++ b/slices/core/src/androidTest/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/slices/core/src/androidTest/java/androidx/app/slice/SliceTest.java b/slices/core/src/androidTest/java/androidx/app/slice/SliceTest.java
new file mode 100644
index 0000000..0ede29d
--- /dev/null
+++ b/slices/core/src/androidTest/java/androidx/app/slice/SliceTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice;
+
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_LIST;
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+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 static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+import static android.app.slice.SliceProvider.SLICE_TYPE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent.CanceledException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import androidx.app.slice.core.test.R;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SliceTest {
+
+    public static boolean sFlag = false;
+
+    private static final Uri BASE_URI = Uri.parse("content://androidx.app.slice.core.test/");
+    private final Context mContext = InstrumentationRegistry.getContext();
+
+    @Test
+    public void testProcess() {
+        sFlag = false;
+        Slice.bindSlice(mContext,
+                BASE_URI.buildUpon().appendPath("set_flag").build(),
+                Collections.<SliceSpec>emptyList());
+        assertFalse(sFlag);
+    }
+
+    @Test
+    public void testType() {
+        assertEquals(SLICE_TYPE, mContext.getContentResolver().getType(BASE_URI));
+    }
+
+    @Test
+    public void testSliceUri() {
+        Slice s = Slice.bindSlice(mContext, BASE_URI, Collections.<SliceSpec>emptyList());
+        assertEquals(BASE_URI, s.getUri());
+    }
+
+    @Test
+    public void testSubSlice() {
+        Uri uri = BASE_URI.buildUpon().appendPath("subslice").build();
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(FORMAT_SLICE, item.getFormat());
+        assertEquals("subslice", item.getSubType());
+        // The item should start with the same Uri as the parent, but be different.
+        assertTrue(item.getSlice().getUri().toString().startsWith(uri.toString()));
+        assertNotEquals(uri, item.getSlice().getUri());
+    }
+
+    @Test
+    public void testText() {
+        Uri uri = BASE_URI.buildUpon().appendPath("text").build();
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(FORMAT_TEXT, item.getFormat());
+        // TODO: Test spannables here.
+        assertEquals("Expected text", item.getText());
+    }
+
+    @Test
+    public void testIcon() {
+        Uri uri = BASE_URI.buildUpon().appendPath("icon").build();
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(FORMAT_IMAGE, item.getFormat());
+        assertEquals(Icon.createWithResource(mContext, R.drawable.size_48x48).toString(),
+                item.getIcon().toString());
+    }
+
+    @Test
+    public void testAction() {
+        sFlag = false;
+        final CountDownLatch latch = new CountDownLatch(1);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                sFlag = true;
+                latch.countDown();
+            }
+        };
+        mContext.registerReceiver(receiver,
+                new IntentFilter(mContext.getPackageName() + ".action"));
+        Uri uri = BASE_URI.buildUpon().appendPath("action").build();
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(FORMAT_ACTION, item.getFormat());
+        try {
+            item.getAction().send();
+        } catch (CanceledException e) {
+        }
+
+        try {
+            latch.await(100, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        assertTrue(sFlag);
+        mContext.unregisterReceiver(receiver);
+    }
+
+    @Test
+    public void testInt() {
+        Uri uri = BASE_URI.buildUpon().appendPath("int").build();
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(FORMAT_INT, item.getFormat());
+        assertEquals(0xff121212, item.getInt());
+    }
+
+    @Test
+    public void testTimestamp() {
+        Uri uri = BASE_URI.buildUpon().appendPath("timestamp").build();
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        assertEquals(uri, s.getUri());
+        assertEquals(1, s.getItems().size());
+
+        SliceItem item = s.getItems().get(0);
+        assertEquals(FORMAT_TIMESTAMP, item.getFormat());
+        assertEquals(43, item.getTimestamp());
+    }
+
+    @Test
+    public void testHints() {
+        // Note this tests that hints are propagated through to the client but not that any specific
+        // hints have any effects.
+        Uri uri = BASE_URI.buildUpon().appendPath("hints").build();
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        assertEquals(uri, s.getUri());
+
+        assertEquals(Arrays.asList(HINT_LIST), s.getHints());
+        assertEquals(Arrays.asList(HINT_TITLE), s.getItems().get(0).getHints());
+        assertEquals(Arrays.asList(HINT_NO_TINT, HINT_LARGE),
+                s.getItems().get(1).getHints());
+    }
+}
diff --git a/slices/core/src/androidTest/java/androidx/app/slice/SliceTestProvider.java b/slices/core/src/androidTest/java/androidx/app/slice/SliceTestProvider.java
new file mode 100644
index 0000000..9320bc9
--- /dev/null
+++ b/slices/core/src/androidTest/java/androidx/app/slice/SliceTestProvider.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice;
+
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_LIST;
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_TITLE;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+
+import androidx.app.slice.Slice.Builder;
+import androidx.app.slice.core.test.R;
+
+public class SliceTestProvider extends androidx.app.slice.SliceProvider {
+
+    @Override
+    public boolean onCreateSliceProvider() {
+        return true;
+    }
+
+    @Override
+    public Slice onBindSlice(Uri sliceUri) {
+        switch (sliceUri.getPath()) {
+            case "/set_flag":
+                SliceTest.sFlag = true;
+                break;
+            case "/subslice":
+                Builder b = new Builder(sliceUri);
+                return b.addSubSlice(new Slice.Builder(b).build(), "subslice").build();
+            case "/text":
+                return new Slice.Builder(sliceUri).addText("Expected text", "text").build();
+            case "/icon":
+                return new Slice.Builder(sliceUri).addIcon(
+                        Icon.createWithResource(getContext(), R.drawable.size_48x48),
+                        "icon").build();
+            case "/action":
+                Builder builder = new Builder(sliceUri);
+                Slice subSlice = new Slice.Builder(builder).build();
+                PendingIntent broadcast = PendingIntent.getBroadcast(getContext(), 0,
+                        new Intent(getContext().getPackageName() + ".action"), 0);
+                return builder.addAction(broadcast, subSlice, "action").build();
+            case "/int":
+                return new Slice.Builder(sliceUri).addInt(0xff121212, "int").build();
+            case "/timestamp":
+                return new Slice.Builder(sliceUri).addTimestamp(43, "timestamp").build();
+            case "/hints":
+                return new Slice.Builder(sliceUri)
+                        .addHints(HINT_LIST)
+                        .addText("Text", null, HINT_TITLE)
+                        .addIcon(Icon.createWithResource(getContext(), R.drawable.size_48x48),
+                                null, HINT_NO_TINT, HINT_LARGE)
+                        .build();
+        }
+        return new Slice.Builder(sliceUri).build();
+    }
+
+}
diff --git a/slices/core/src/androidTest/res/drawable/size_48x48.jpg b/slices/core/src/androidTest/res/drawable/size_48x48.jpg
new file mode 100644
index 0000000..5c2291e
--- /dev/null
+++ b/slices/core/src/androidTest/res/drawable/size_48x48.jpg
Binary files differ
diff --git a/slices/core/src/main/AndroidManifest.xml b/slices/core/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ca86418
--- /dev/null
+++ b/slices/core/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.app.slice.core">
+    <uses-sdk android:minSdkVersion="28"/>
+</manifest>
diff --git a/slices/core/src/main/java/androidx/app/slice/ArrayUtils.java b/slices/core/src/main/java/androidx/app/slice/ArrayUtils.java
new file mode 100644
index 0000000..669f66a
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/ArrayUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 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.
+ */
+package androidx.app.slice;
+
+
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+
+import java.lang.reflect.Array;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+class ArrayUtils {
+
+    public static <T> boolean contains(T[] array, T item) {
+        for (T t : array) {
+            if (Objects.equals(t, item)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static <T> T[] appendElement(Class<T> kind, T[] array, T element) {
+        final T[] result;
+        final int end;
+        if (array != null) {
+            end = array.length;
+            result = (T[]) Array.newInstance(kind, end + 1);
+            System.arraycopy(array, 0, result, 0, end);
+        } else {
+            end = 0;
+            result = (T[]) Array.newInstance(kind, 1);
+        }
+        result[end] = element;
+        return result;
+    }
+
+    public static <T> T[] removeElement(Class<T> kind, T[] array, T element) {
+        if (array != null) {
+            if (!contains(array, element)) {
+                return array;
+            }
+            final int length = array.length;
+            for (int i = 0; i < length; i++) {
+                if (Objects.equals(array[i], element)) {
+                    if (length == 1) {
+                        return null;
+                    }
+                    T[] result = (T[]) Array.newInstance(kind, length - 1);
+                    System.arraycopy(array, 0, result, 0, i);
+                    System.arraycopy(array, i + 1, result, i, length - i - 1);
+                    return result;
+                }
+            }
+        }
+        return array;
+    }
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/Slice.java b/slices/core/src/main/java/androidx/app/slice/Slice.java
new file mode 100644
index 0000000..d0137b1
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/Slice.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package androidx.app.slice;
+
+import static android.app.slice.Slice.HINT_ACTIONS;
+import static android.app.slice.Slice.HINT_HORIZONTAL;
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_LIST;
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_PARTIAL;
+import static android.app.slice.Slice.HINT_SELECTED;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_INT;
+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 android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import static androidx.app.slice.SliceConvert.unwrap;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+import android.support.annotation.StringDef;
+import android.support.v4.os.BuildCompat;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.app.slice.compat.SliceProviderCompat;
+import androidx.app.slice.core.SliceHints;
+
+/**
+ * A slice is a piece of app content and actions that can be surfaced outside of the app.
+ *
+ * <p>They are constructed using {@link androidx.app.slice.builders.TemplateSliceBuilder}s
+ * in a tree structure that provides the OS some information about how the content should be
+ * displayed.
+ */
+public final class Slice {
+
+    private static final String HINTS = "hints";
+    private static final String ITEMS = "items";
+    private static final String URI = "uri";
+    private static final String SPEC_TYPE = "type";
+    private static final String SPEC_REVISION = "revision";
+    private final SliceSpec mSpec;
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
+            HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL,
+            SliceHints.HINT_SUMMARY, SliceHints.SUBTYPE_TOGGLE})
+    public @interface SliceHint{ }
+
+    private final SliceItem[] mItems;
+    private final @SliceHint String[] mHints;
+    private Uri mUri;
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri,
+            SliceSpec spec) {
+        mHints = hints;
+        mItems = items.toArray(new SliceItem[items.size()]);
+        mUri = uri;
+        mSpec = spec;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public Slice(Bundle in) {
+        mHints = in.getStringArray(HINTS);
+        Parcelable[] items = in.getParcelableArray(ITEMS);
+        mItems = new SliceItem[items.length];
+        for (int i = 0; i < mItems.length; i++) {
+            if (items[i] instanceof Bundle) {
+                mItems[i] = new SliceItem((Bundle) items[i]);
+            }
+        }
+        mUri = in.getParcelable(URI);
+        mSpec = in.containsKey(SPEC_TYPE)
+                ? new SliceSpec(in.getString(SPEC_TYPE), in.getInt(SPEC_REVISION))
+                : null;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public Bundle toBundle() {
+        Bundle b = new Bundle();
+        b.putStringArray(HINTS, mHints);
+        Parcelable[] p = new Parcelable[mItems.length];
+        for (int i = 0; i < mItems.length; i++) {
+            p[i] = mItems[i].toBundle();
+        }
+        b.putParcelableArray(ITEMS, p);
+        b.putParcelable(URI, mUri);
+        if (mSpec != null) {
+            b.putString(SPEC_TYPE, mSpec.getType());
+            b.putInt(SPEC_REVISION, mSpec.getRevision());
+        }
+        return b;
+    }
+
+    /**
+     * @return The spec for this slice
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public @Nullable SliceSpec getSpec() {
+        return mSpec;
+    }
+
+    /**
+     * @return The Uri that this Slice represents.
+     */
+    public Uri getUri() {
+        return mUri;
+    }
+
+    /**
+     * @return All child {@link SliceItem}s that this Slice contains.
+     */
+    public List<SliceItem> getItems() {
+        return Arrays.asList(mItems);
+    }
+
+    /**
+     * @return All hints associated with this Slice.
+     */
+    public @SliceHint List<String> getHints() {
+        return Arrays.asList(mHints);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public boolean hasHint(@SliceHint String hint) {
+        return ArrayUtils.contains(mHints, hint);
+    }
+
+    /**
+     * A Builder used to construct {@link Slice}s
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public static class Builder {
+
+        private final Uri mUri;
+        private ArrayList<SliceItem> mItems = new ArrayList<>();
+        private @SliceHint ArrayList<String> mHints = new ArrayList<>();
+        private SliceSpec mSpec;
+
+        /**
+         * Create a builder which will construct a {@link Slice} for the Given Uri.
+         * @param uri Uri to tag for this slice.
+         */
+        public Builder(@NonNull Uri uri) {
+            mUri = uri;
+        }
+
+        /**
+         * Create a builder for a {@link Slice} that is a sub-slice of the slice
+         * being constructed by the provided builder.
+         * @param parent The builder constructing the parent slice
+         */
+        public Builder(@NonNull Slice.Builder parent) {
+            mUri = parent.mUri.buildUpon().appendPath("_gen")
+                    .appendPath(String.valueOf(mItems.size())).build();
+        }
+
+        /**
+         * Add the spec for this slice.
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        public Builder setSpec(SliceSpec spec) {
+            mSpec = spec;
+            return this;
+        }
+
+        /**
+         * Add hints to the Slice being constructed
+         */
+        public Builder addHints(@SliceHint String... hints) {
+            mHints.addAll(Arrays.asList(hints));
+            return this;
+        }
+
+        /**
+         * Add hints to the Slice being constructed
+         */
+        public Builder addHints(@SliceHint List<String> hints) {
+            return addHints(hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add a sub-slice to the slice being constructed
+         */
+        public Builder addSubSlice(@NonNull Slice slice) {
+            return addSubSlice(slice, null);
+        }
+
+        /**
+         * Add a sub-slice to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addSubSlice(@NonNull Slice slice, String subType) {
+            mItems.add(new SliceItem(slice, FORMAT_SLICE, subType, slice.getHints().toArray(
+                    new String[slice.getHints().size()])));
+            return this;
+        }
+
+        /**
+         * Add an action to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Slice.Builder addAction(@NonNull PendingIntent action,
+                @NonNull Slice s, @Nullable String subType) {
+            mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, new String[0]));
+            return this;
+        }
+
+        /**
+         * Add text to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addText(CharSequence text, @Nullable String subType,
+                @SliceHint String... hints) {
+            mItems.add(new SliceItem(text, FORMAT_TEXT, subType, hints));
+            return this;
+        }
+
+        /**
+         * Add text to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addText(CharSequence text, @Nullable String subType,
+                @SliceHint List<String> hints) {
+            return addText(text, subType, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add an image to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addIcon(Icon icon, @Nullable String subType,
+                @SliceHint String... hints) {
+            mItems.add(new SliceItem(icon, FORMAT_IMAGE, subType, hints));
+            return this;
+        }
+
+        /**
+         * Add an image to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addIcon(Icon icon, @Nullable String subType,
+                @SliceHint List<String> hints) {
+            return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add remote input to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+                @SliceHint List<String> hints) {
+            return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add remote input to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+                @SliceHint String... hints) {
+            mItems.add(new SliceItem(remoteInput, FORMAT_REMOTE_INPUT, subType, hints));
+            return this;
+        }
+
+        /**
+         * Add a int to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addInt(int value, @Nullable String subType,
+                @SliceHint String... hints) {
+            mItems.add(new SliceItem(value, FORMAT_INT, subType, hints));
+            return this;
+        }
+
+        /**
+         * Add a int to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Builder addInt(int value, @Nullable String subType,
+                @SliceHint List<String> hints) {
+            return addInt(value, subType, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add a timestamp to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Slice.Builder addTimestamp(long time, @Nullable String subType,
+                @SliceHint String... hints) {
+            mItems.add(new SliceItem(time, FORMAT_TIMESTAMP, subType, hints));
+            return this;
+        }
+
+        /**
+         * Add a timestamp to the slice being constructed
+         * @param subType Optional template-specific type information
+         * @see {@link SliceItem#getSubType()}
+         */
+        public Slice.Builder addTimestamp(long time, @Nullable String subType,
+                @SliceHint List<String> hints) {
+            return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add a SliceItem to the slice being constructed.
+         * @hide
+         */
+        @RestrictTo(Scope.LIBRARY)
+        public Slice.Builder addItem(SliceItem item) {
+            mItems.add(item);
+            return this;
+        }
+
+        /**
+         * Construct the slice.
+         */
+        public Slice build() {
+            return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
+        }
+    }
+
+    /**
+     * @hide
+     * @return A string representation of this slice.
+     */
+    @RestrictTo(Scope.LIBRARY)
+    @Override
+    public String toString() {
+        return toString("");
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public String toString(String indent) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < mItems.length; i++) {
+            sb.append(indent);
+            if (FORMAT_SLICE.equals(mItems[i].getFormat())) {
+                sb.append("slice:\n");
+                sb.append(mItems[i].getSlice().toString(indent + "   "));
+            } else if (FORMAT_ACTION.equals(mItems[i].getFormat())) {
+                sb.append("action:\n");
+                sb.append(mItems[i].getSlice().toString(indent + "   "));
+            } else if (FORMAT_TEXT.equals(mItems[i].getFormat())) {
+                sb.append("text: ");
+                sb.append(mItems[i].getText());
+                sb.append("\n");
+            } else {
+                sb.append(SliceItem.typeToString(mItems[i].getFormat()));
+                sb.append("\n");
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     */
+    public static @Nullable Slice bindSlice(Context context, @NonNull Uri uri) {
+        throw new RuntimeException("Stub, to be removed");
+    }
+
+    /**
+     */
+    public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
+        throw new RuntimeException("Stub, to be removed");
+    }
+
+    /**
+     * Turns a slice Uri into slice content.
+     *
+     * @hide
+     * @param context Context to be used.
+     * @param uri The URI to a slice provider
+     * @return The Slice provided by the app or null if none is given.
+     * @see Slice
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @SuppressWarnings("NewApi")
+    public static @Nullable Slice bindSlice(Context context, @NonNull Uri uri,
+                List<SliceSpec> supportedSpecs) {
+        if (BuildCompat.isAtLeastP()) {
+            return callBindSlice(context, uri, supportedSpecs);
+        } else {
+            return SliceProviderCompat.bindSlice(context, uri, supportedSpecs);
+        }
+    }
+
+    @TargetApi(28)
+    private static Slice callBindSlice(Context context, Uri uri,
+            List<SliceSpec> supportedSpecs) {
+        return SliceConvert.wrap(android.app.slice.Slice.bindSlice(
+                context.getContentResolver(), uri, unwrap(supportedSpecs)));
+    }
+
+
+    /**
+     * Turns a slice intent into slice content. Expects an explicit intent. If there is no
+     * {@link ContentProvider} associated with the given intent this will throw
+     * {@link IllegalArgumentException}.
+     *
+     * @hide
+     * @param context The context to use.
+     * @param intent The intent associated with a slice.
+     * @return The Slice provided by the app or null if none is given.
+     * @see Slice
+     * @see SliceProvider#onMapIntentToUri(Intent)
+     * @see Intent
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @SuppressWarnings("NewApi")
+    public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent,
+                List<SliceSpec> supportedSpecs) {
+        if (BuildCompat.isAtLeastP()) {
+            return callBindSlice(context, intent, supportedSpecs);
+        } else {
+            return SliceProviderCompat.bindSlice(context, intent, supportedSpecs);
+        }
+    }
+
+    @TargetApi(28)
+    private static Slice callBindSlice(Context context, Intent intent,
+            List<SliceSpec> supportedSpecs) {
+        return SliceConvert.wrap(android.app.slice.Slice.bindSlice(
+                context, intent, unwrap(supportedSpecs)));
+    }
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/SliceConvert.java b/slices/core/src/main/java/androidx/app/slice/SliceConvert.java
new file mode 100644
index 0000000..0bacae7
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/SliceConvert.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 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.
+ */
+package androidx.app.slice;
+
+
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_INT;
+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 android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Convert between {@link androidx.app.slice.Slice} and {@link android.app.slice.Slice}
+ */
+@RequiresApi(28)
+public class SliceConvert {
+
+    /**
+     * Convert {@link androidx.app.slice.Slice} to {@link android.app.slice.Slice}
+     */
+    public static android.app.slice.Slice unwrap(androidx.app.slice.Slice slice) {
+        android.app.slice.Slice.Builder builder = new android.app.slice.Slice.Builder(
+                slice.getUri());
+        builder.addHints(slice.getHints());
+        builder.setSpec(unwrap(slice.getSpec()));
+        for (androidx.app.slice.SliceItem item : slice.getItems()) {
+            switch (item.getFormat()) {
+                case FORMAT_SLICE:
+                    builder.addSubSlice(unwrap(item.getSlice()), item.getSubType());
+                    break;
+                case FORMAT_IMAGE:
+                    builder.addIcon(item.getIcon(), item.getSubType(), item.getHints());
+                    break;
+                case FORMAT_REMOTE_INPUT:
+                    builder.addRemoteInput(item.getRemoteInput(), item.getSubType(),
+                            item.getHints());
+                    break;
+                case FORMAT_ACTION:
+                    builder.addAction(item.getAction(), unwrap(item.getSlice()), item.getSubType());
+                    break;
+                case FORMAT_TEXT:
+                    builder.addText(item.getText(), item.getSubType(), item.getHints());
+                    break;
+                case FORMAT_INT:
+                    builder.addInt(item.getInt(), item.getSubType(), item.getHints());
+                    break;
+                case FORMAT_TIMESTAMP:
+                    builder.addTimestamp(item.getTimestamp(), item.getSubType(), item.getHints());
+                    break;
+            }
+        }
+        return builder.build();
+    }
+
+    private static android.app.slice.SliceSpec unwrap(androidx.app.slice.SliceSpec spec) {
+        if (spec == null) return null;
+        return new android.app.slice.SliceSpec(spec.getType(), spec.getRevision());
+    }
+
+    static List<android.app.slice.SliceSpec> unwrap(
+            List<androidx.app.slice.SliceSpec> supportedSpecs) {
+        List<android.app.slice.SliceSpec> ret = new ArrayList<>();
+        for (androidx.app.slice.SliceSpec spec : supportedSpecs) {
+            ret.add(unwrap(spec));
+        }
+        return ret;
+    }
+
+    /**
+     * Convert {@link android.app.slice.Slice} to {@link androidx.app.slice.Slice}
+     */
+    public static androidx.app.slice.Slice wrap(android.app.slice.Slice slice) {
+        androidx.app.slice.Slice.Builder builder = new androidx.app.slice.Slice.Builder(
+                slice.getUri());
+        builder.addHints(slice.getHints());
+        builder.setSpec(wrap(slice.getSpec()));
+        for (android.app.slice.SliceItem item : slice.getItems()) {
+            switch (item.getFormat()) {
+                case FORMAT_SLICE:
+                    builder.addSubSlice(wrap(item.getSlice()), item.getSubType());
+                    break;
+                case FORMAT_IMAGE:
+                    builder.addIcon(item.getIcon(), item.getSubType(), item.getHints());
+                    break;
+                case FORMAT_REMOTE_INPUT:
+                    builder.addRemoteInput(item.getRemoteInput(), item.getSubType(),
+                            item.getHints());
+                    break;
+                case FORMAT_ACTION:
+                    builder.addAction(item.getAction(), wrap(item.getSlice()), item.getSubType());
+                    break;
+                case FORMAT_TEXT:
+                    builder.addText(item.getText(), item.getSubType(), item.getHints());
+                    break;
+                case FORMAT_INT:
+                    builder.addInt(item.getInt(), item.getSubType(), item.getHints());
+                    break;
+                case FORMAT_TIMESTAMP:
+                    builder.addTimestamp(item.getTimestamp(), item.getSubType(), item.getHints());
+                    break;
+            }
+        }
+        return builder.build();
+    }
+
+    private static androidx.app.slice.SliceSpec wrap(android.app.slice.SliceSpec spec) {
+        if (spec == null) return null;
+        return new androidx.app.slice.SliceSpec(spec.getType(), spec.getRevision());
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public static List<androidx.app.slice.SliceSpec> wrap(
+            List<android.app.slice.SliceSpec> supportedSpecs) {
+        List<androidx.app.slice.SliceSpec> ret = new ArrayList<>();
+        for (android.app.slice.SliceSpec spec : supportedSpecs) {
+            ret.add(wrap(spec));
+        }
+        return ret;
+    }
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/SliceItem.java b/slices/core/src/main/java/androidx/app/slice/SliceItem.java
new file mode 100644
index 0000000..e7d2729
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/SliceItem.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package androidx.app.slice;
+
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_INT;
+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 android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+import android.support.annotation.StringDef;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * A SliceItem is a single unit in the tree structure of a {@link Slice}.
+ * <p>
+ * A SliceItem a piece of content and some hints about what that content
+ * means or how it should be displayed. The types of content can be:
+ * <li>{@link android.app.slice.SliceItem#FORMAT_SLICE}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_TEXT}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_IMAGE}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_ACTION}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_INT}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_TIMESTAMP}</li>
+ * <p>
+ * The hints that a {@link SliceItem} are a set of strings which annotate
+ * the content. The hints that are guaranteed to be understood by the system
+ * are defined on {@link Slice}.
+ */
+public class SliceItem {
+
+    private static final String HINTS = "hints";
+    private static final String FORMAT = "format";
+    private static final String SUBTYPE = "subtype";
+    private static final String OBJ = "obj";
+    private static final String OBJ_2 = "obj_2";
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_INT,
+            FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT})
+    public @interface SliceType {
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    protected @Slice.SliceHint String[] mHints;
+    private final String mFormat;
+    private final String mSubType;
+    private final Object mObj;
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public SliceItem(Object obj, @SliceType String format, String subType,
+            @Slice.SliceHint String[] hints) {
+        mHints = hints;
+        mFormat = format;
+        mSubType = subType;
+        mObj = obj;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public SliceItem(Object obj, @SliceType String format, String subType,
+            @Slice.SliceHint List<String> hints) {
+        this (obj, format, subType, hints.toArray(new String[hints.size()]));
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public SliceItem(PendingIntent intent, Slice slice, String format, String subType,
+            @Slice.SliceHint String[] hints) {
+        this(new Pair<>(intent, slice), format, subType, hints);
+    }
+
+    /**
+     * Gets all hints associated with this SliceItem.
+     *
+     * @return Array of hints.
+     */
+    public @NonNull @Slice.SliceHint List<String> getHints() {
+        return Arrays.asList(mHints);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public void addHint(@Slice.SliceHint String hint) {
+        mHints = ArrayUtils.appendElement(String.class, mHints, hint);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public void removeHint(String hint) {
+        ArrayUtils.removeElement(String.class, mHints, hint);
+    }
+
+    /**
+     * Get the format of this SliceItem.
+     * <p>
+     * The format will be one of the following types supported by the platform:
+     * <li>{@link android.app.slice.SliceItem#FORMAT_SLICE}</li>
+     * <li>{@link android.app.slice.SliceItem#FORMAT_TEXT}</li>
+     * <li>{@link android.app.slice.SliceItem#FORMAT_IMAGE}</li>
+     * <li>{@link android.app.slice.SliceItem#FORMAT_ACTION}</li>
+     * <li>{@link android.app.slice.SliceItem#FORMAT_INT}</li>
+     * <li>{@link android.app.slice.SliceItem#FORMAT_TIMESTAMP}</li>
+     * <li>{@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT}</li>
+     * @see #getSubType() ()
+     */
+    public @SliceType String getFormat() {
+        return mFormat;
+    }
+
+    /**
+     * Get the sub-type of this SliceItem.
+     * <p>
+     * Subtypes provide additional information about the type of this information beyond basic
+     * interpretations inferred by {@link #getFormat()}. For example a slice may contain
+     * many {@link android.app.slice.SliceItem#FORMAT_TEXT} items, but only some of them may be
+     * {@link android.app.slice.Slice#SUBTYPE_MESSAGE}.
+     * @see #getFormat()
+     */
+    public String getSubType() {
+        return mSubType;
+    }
+
+    /**
+     * @return The text held by this {@link android.app.slice.SliceItem#FORMAT_TEXT} SliceItem
+     */
+    public CharSequence getText() {
+        return (CharSequence) mObj;
+    }
+
+    /**
+     * @return The icon held by this {@link android.app.slice.SliceItem#FORMAT_IMAGE} SliceItem
+     */
+    @RequiresApi(23)
+    public Icon getIcon() {
+        return (Icon) mObj;
+    }
+
+    /**
+     * @return The pending intent held by this {@link android.app.slice.SliceItem#FORMAT_ACTION}
+     * SliceItem
+     */
+    public PendingIntent getAction() {
+        return ((Pair<PendingIntent, Slice>) mObj).first;
+    }
+
+    /**
+     * @return The remote input held by this {@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT}
+     * SliceItem
+     * @hide
+     */
+    @RequiresApi(20)
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public RemoteInput getRemoteInput() {
+        return (RemoteInput) mObj;
+    }
+
+    /**
+     * @return The color held by this {@link android.app.slice.SliceItem#FORMAT_INT} SliceItem
+     */
+    public int getInt() {
+        return (Integer) mObj;
+    }
+
+    /**
+     * @return The slice held by this {@link android.app.slice.SliceItem#FORMAT_ACTION} or
+     * {@link android.app.slice.SliceItem#FORMAT_SLICE} SliceItem
+     */
+    public Slice getSlice() {
+        if (FORMAT_ACTION.equals(getFormat())) {
+            return ((Pair<PendingIntent, Slice>) mObj).second;
+        }
+        return (Slice) mObj;
+    }
+
+    /**
+     * @return The timestamp held by this {@link android.app.slice.SliceItem#FORMAT_TIMESTAMP}
+     * SliceItem
+     */
+    public long getTimestamp() {
+        return (Long) mObj;
+    }
+
+    /**
+     * @param hint The hint to check for
+     * @return true if this item contains the given hint
+     */
+    public boolean hasHint(@Slice.SliceHint String hint) {
+        return ArrayUtils.contains(mHints, hint);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public SliceItem(Bundle in) {
+        mHints = in.getStringArray(HINTS);
+        mFormat = in.getString(FORMAT);
+        mSubType = in.getString(SUBTYPE);
+        mObj = readObj(mFormat, in);
+    }
+
+    /**
+     * @hide
+     * @return
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public Bundle toBundle() {
+        Bundle b = new Bundle();
+        b.putStringArray(HINTS, mHints);
+        b.putString(FORMAT, mFormat);
+        b.putString(SUBTYPE, mSubType);
+        writeObj(b, mObj, mFormat);
+        return b;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public boolean hasHints(@Slice.SliceHint String[] hints) {
+        if (hints == null) return true;
+        for (String hint : hints) {
+            if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public boolean hasAnyHints(@Slice.SliceHint String... hints) {
+        if (hints == null) return false;
+        for (String hint : hints) {
+            if (ArrayUtils.contains(mHints, hint)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void writeObj(Bundle dest, Object obj, String type) {
+        switch (type) {
+            case FORMAT_IMAGE:
+            case FORMAT_REMOTE_INPUT:
+                dest.putParcelable(OBJ, (Parcelable) obj);
+                break;
+            case FORMAT_SLICE:
+                dest.putParcelable(OBJ, ((Slice) obj).toBundle());
+                break;
+            case FORMAT_ACTION:
+                dest.putParcelable(OBJ, ((Pair<PendingIntent, Slice>) obj).first);
+                dest.putBundle(OBJ_2, ((Pair<PendingIntent, Slice>) obj).second.toBundle());
+                break;
+            case FORMAT_TEXT:
+                dest.putCharSequence(OBJ, (CharSequence) obj);
+                break;
+            case FORMAT_INT:
+                dest.putInt(OBJ, (Integer) mObj);
+                break;
+            case FORMAT_TIMESTAMP:
+                dest.putLong(OBJ, (Long) mObj);
+                break;
+        }
+    }
+
+    private static Object readObj(String type, Bundle in) {
+        switch (type) {
+            case FORMAT_IMAGE:
+            case FORMAT_REMOTE_INPUT:
+                return in.getParcelable(OBJ);
+            case FORMAT_SLICE:
+                return new Slice(in.getBundle(OBJ));
+            case FORMAT_TEXT:
+                return in.getCharSequence(OBJ);
+            case FORMAT_ACTION:
+                return new Pair<>(
+                        (PendingIntent) in.getParcelable(OBJ),
+                        new Slice(in.getBundle(OBJ_2)));
+            case FORMAT_INT:
+                return in.getInt(OBJ);
+            case FORMAT_TIMESTAMP:
+                return in.getLong(OBJ);
+        }
+        throw new RuntimeException("Unsupported type " + type);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
+    public static String typeToString(String format) {
+        switch (format) {
+            case FORMAT_SLICE:
+                return "Slice";
+            case FORMAT_TEXT:
+                return "Text";
+            case FORMAT_IMAGE:
+                return "Image";
+            case FORMAT_ACTION:
+                return "Action";
+            case FORMAT_INT:
+                return "Int";
+            case FORMAT_TIMESTAMP:
+                return "Timestamp";
+            case FORMAT_REMOTE_INPUT:
+                return "RemoteInput";
+        }
+        return "Unrecognized format: " + format;
+    }
+
+    /**
+     * @hide
+     * @return A string representation of this slice item.
+     */
+    @RestrictTo(Scope.LIBRARY)
+    @Override
+    public String toString() {
+        return toString("");
+    }
+
+    private String toString(String indent) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(indent);
+        if (FORMAT_SLICE.equals(mFormat)) {
+            sb.append("slice:\n");
+            sb.append(getSlice().toString(indent + "   "));
+        } else if (FORMAT_ACTION.equals(mFormat)) {
+            sb.append("action:\n");
+            sb.append(getSlice().toString(indent + "   "));
+        } else if (FORMAT_TEXT.equals(mFormat)) {
+            sb.append("text: ");
+            sb.append(getText());
+            sb.append("\n");
+        } else {
+            sb.append(SliceItem.typeToString(getFormat()));
+            sb.append("\n");
+        }
+        return sb.toString();
+    }
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/SliceProvider.java b/slices/core/src/main/java/androidx/app/slice/SliceProvider.java
new file mode 100644
index 0000000..8ec2dbe
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/SliceProvider.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 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.
+ */
+package androidx.app.slice;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ProviderInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.v4.os.BuildCompat;
+
+import java.util.List;
+
+import androidx.app.slice.compat.ContentProviderWrapper;
+import androidx.app.slice.compat.SliceProviderCompat;
+import androidx.app.slice.compat.SliceProviderWrapper;
+
+/**
+ * A SliceProvider allows an app to provide content to be displayed in system spaces. This content
+ * is templated and can contain actions, and the behavior of how it is surfaced is specific to the
+ * system surface.
+ * <p>
+ * Slices are not currently live content. They are bound once and shown to the user. If the content
+ * changes due to a callback from user interaction, then
+ * {@link ContentResolver#notifyChange(Uri, ContentObserver)} should be used to notify the system.
+ * </p>
+ * <p>
+ * The provider needs to be declared in the manifest to provide the authority for the app. The
+ * authority for most slices is expected to match the package of the application.
+ * </p>
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <provider
+ *     android:name="com.android.mypkg.MySliceProvider"
+ *     android:authorities="com.android.mypkg" />}
+ * </pre>
+ * <p>
+ * Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider
+ * must have an {@link IntentFilter} matching the slice intent. When a slice is being requested via
+ * an intent, {@link #onMapIntentToUri(Intent)} can be called and is expected to return an
+ * appropriate Uri representing the slice.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <provider
+ *     android:name="com.android.mypkg.MySliceProvider"
+ *     android:authorities="com.android.mypkg">
+ *     <intent-filter>
+ *         <action android:name="android.intent.action.MY_SLICE_INTENT" />
+ *     </intent-filter>
+ * </provider>}
+ * </pre>
+ *
+ * @see android.app.slice.Slice
+ */
+public abstract class SliceProvider extends ContentProviderWrapper {
+
+    private static List<SliceSpec> sSpecs;
+
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+        ContentProvider impl;
+        if (BuildCompat.isAtLeastP()) {
+            impl = new SliceProviderWrapper(this);
+        } else {
+            impl = new SliceProviderCompat(this);
+        }
+        super.attachInfo(context, info, impl);
+    }
+
+    /**
+     * Implement this to initialize your slice provider on startup.
+     * This method is called for all registered slice providers on the
+     * application main thread at application launch time.  It must not perform
+     * lengthy operations, or application startup will be delayed.
+     *
+     * <p>You should defer nontrivial initialization (such as opening,
+     * upgrading, and scanning databases) until the slice provider is used
+     * (via #onBindSlice, etc).  Deferred initialization
+     * keeps application startup fast, avoids unnecessary work if the provider
+     * turns out not to be needed, and stops database errors (such as a full
+     * disk) from halting application launch.
+     *
+     * @return true if the provider was successfully loaded, false otherwise
+     */
+    public abstract boolean onCreateSliceProvider();
+
+    /**
+     * Implemented to create a slice. Will be called on the main thread.
+     * <p>
+     * onBindSlice should return as quickly as possible so that the UI tied
+     * to this slice can be responsive. No network or other IO will be allowed
+     * during onBindSlice. Any loading that needs to be done should happen
+     * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
+     * when the app is ready to provide the complete data in onBindSlice.
+     * <p>
+     *
+     * @see {@link Slice}.
+     * @see {@link android.app.slice.Slice#HINT_PARTIAL}
+     */
+    // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
+    public abstract Slice onBindSlice(Uri sliceUri);
+
+    /**
+     * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
+     * In that case, this method can be called and is expected to return a non-null Uri representing
+     * a slice. Otherwise this will throw {@link UnsupportedOperationException}.
+     *
+     * @return Uri representing the slice associated with the provided intent.
+     * @see {@link android.app.slice.Slice}
+     */
+    public @NonNull Uri onMapIntentToUri(Intent intent) {
+        throw new UnsupportedOperationException(
+                "This provider has not implemented intent to uri mapping");
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public static void setSpecs(List<SliceSpec> specs) {
+        sSpecs = specs;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static List<SliceSpec> getCurrentSpecs() {
+        return sSpecs;
+    }
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/SliceSpec.java b/slices/core/src/main/java/androidx/app/slice/SliceSpec.java
new file mode 100644
index 0000000..0d7a157
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/SliceSpec.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice;
+
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Class describing the structure of the data contained within a slice.
+ * <p>
+ * A data version contains a string which describes the type of structure
+ * and a revision which denotes this specific implementation. Revisions are expected
+ * to be backwards compatible and monotonically increasing. Meaning if a
+ * SliceSpec has the same type and an equal or lesser revision,
+ * it is expected to be compatible.
+ * <p>
+ * Apps rendering slices will provide a list of supported versions to the OS which
+ * will also be given to the app. Apps should only return a {@link Slice} with a
+ * {@link SliceSpec} that one of the supported {@link SliceSpec}s provided
+ * {@link #canRender}.
+ *
+ * @hide
+ * @see Slice
+ * @see SliceProvider#onBindSlice(Uri)
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SliceSpec {
+
+    private final String mType;
+    private final int mRevision;
+
+    public SliceSpec(@NonNull String type, int revision) {
+        mType = type;
+        mRevision = revision;
+    }
+
+    /**
+     * Gets the type of the version.
+     */
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Gets the revision of the version.
+     */
+    public int getRevision() {
+        return mRevision;
+    }
+
+    /**
+     * Indicates that this spec can be used to render the specified spec.
+     * <p>
+     * Rendering support is not bi-directional (e.g. Spec v3 can render
+     * Spec v2, but Spec v2 cannot render Spec v3).
+     *
+     * @param candidate candidate format of data.
+     * @return true if versions are compatible.
+     * @see androidx.app.slice.widget.SliceView
+     */
+    public boolean canRender(@NonNull SliceSpec candidate) {
+        if (!mType.equals(candidate.mType)) return false;
+        return mRevision >= candidate.mRevision;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof SliceSpec)) return false;
+        SliceSpec other = (SliceSpec) obj;
+        return mType.equals(other.mType) && mRevision == other.mRevision;
+    }
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/SliceSpecs.java b/slices/core/src/main/java/androidx/app/slice/SliceSpecs.java
new file mode 100644
index 0000000..ed4658d
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/SliceSpecs.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * Constants for each of the slice specs
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SliceSpecs {
+
+    /**
+     * Most basic slice, only has icon, title, and summary.
+     */
+    public static final SliceSpec BASIC = new SliceSpec("androidx.app.slice.BASIC", 1);
+
+    /**
+     * List of rows, each row has start/end items, title, summary.
+     * Also supports grid rows.
+     */
+    public static final SliceSpec LIST = new SliceSpec("androidx.app.slice.LIST", 1);
+
+    /**
+     * Messaging template. Each message contains a timestamp and a message, it optionally contains
+     * a source of where the message came from.
+     */
+    public static final SliceSpec MESSAGING = new SliceSpec("androidx.app.slice.MESSAGING", 1);
+
+    /**
+     * Grid template.
+     * Lists can contain grids, so use the same spec for both. Grid needs a spec to use because
+     * it can be a top level builder.
+     */
+    public static final SliceSpec GRID = LIST;
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/compat/ContentProviderWrapper.java b/slices/core/src/main/java/androidx/app/slice/compat/ContentProviderWrapper.java
new file mode 100644
index 0000000..9e02b3a
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/compat/ContentProviderWrapper.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 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.
+ */
+package androidx.app.slice.compat;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+
+/**
+ * @hide
+ */
+// TODO: Remove as soon as we have better systems in place for this.
+@RestrictTo(Scope.LIBRARY)
+public class ContentProviderWrapper extends ContentProvider {
+
+    private ContentProvider mImpl;
+
+    /**
+     * Triggers an attach with the object to wrap.
+     */
+    public void attachInfo(Context context, ProviderInfo info, ContentProvider impl) {
+        mImpl = impl;
+        super.attachInfo(context, info);
+        mImpl.attachInfo(context, info);
+    }
+
+    @Override
+    public final boolean onCreate() {
+        return mImpl.onCreate();
+    }
+
+    @Nullable
+    @Override
+    public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+            @Nullable String selection, @Nullable String[] selectionArgs,
+            @Nullable String sortOrder) {
+        return mImpl.query(uri, projection, selection, selectionArgs, sortOrder);
+    }
+
+    @Nullable
+    @Override
+    @RequiresApi(28)
+    public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+            @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
+        return mImpl.query(uri, projection, queryArgs, cancellationSignal);
+    }
+
+    @Nullable
+    @Override
+    public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+            @Nullable String selection, @Nullable String[] selectionArgs,
+            @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
+        return mImpl.query(uri, projection, selection, selectionArgs, sortOrder,
+                cancellationSignal);
+    }
+
+    @Nullable
+    @Override
+    public final String getType(@NonNull Uri uri) {
+        return mImpl.getType(uri);
+    }
+
+    @Nullable
+    @Override
+    public final Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+        return mImpl.insert(uri, values);
+    }
+
+    @Override
+    public final int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
+        return mImpl.bulkInsert(uri, values);
+    }
+
+    @Override
+    public final int delete(@NonNull Uri uri, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
+        return mImpl.delete(uri, selection, selectionArgs);
+    }
+
+    @Override
+    public final int update(@NonNull Uri uri, @Nullable ContentValues values,
+            @Nullable String selection, @Nullable String[] selectionArgs) {
+        return mImpl.update(uri, values, selection, selectionArgs);
+    }
+
+    @Nullable
+    @Override
+    public final Bundle call(@NonNull String method, @Nullable String arg,
+            @Nullable Bundle extras) {
+        return mImpl.call(method, arg, extras);
+    }
+
+    @Nullable
+    @Override
+    public final Uri canonicalize(@NonNull Uri url) {
+        return mImpl.canonicalize(url);
+    }
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderCompat.java b/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderCompat.java
new file mode 100644
index 0000000..1786f0e
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderCompat.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 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.
+ */
+package androidx.app.slice.compat;
+
+import static android.app.slice.SliceProvider.SLICE_TYPE;
+
+import android.Manifest.permission;
+import android.content.ContentProvider;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.SliceSpec;
+
+/**
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class SliceProviderCompat extends ContentProvider {
+
+    private static final String TAG = "SliceProvider";
+
+    public static final String EXTRA_BIND_URI = "slice_uri";
+    public static final String METHOD_SLICE = "bind_slice";
+    public static final String METHOD_MAP_INTENT = "map_slice";
+    public static final String EXTRA_INTENT = "slice_intent";
+    public static final String EXTRA_SLICE = "slice";
+    public static final String EXTRA_SUPPORTED_SPECS = "specs";
+    public static final String EXTRA_SUPPORTED_SPECS_REVS = "revs";
+
+    private static final boolean DEBUG = false;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private SliceProvider mSliceProvider;
+
+    public SliceProviderCompat(SliceProvider provider) {
+        mSliceProvider = provider;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return mSliceProvider.onCreateSliceProvider();
+    }
+
+    @Override
+    public final int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        if (DEBUG) Log.d(TAG, "update " + uri);
+        return 0;
+    }
+
+    @Override
+    public final int delete(Uri uri, String selection, String[] selectionArgs) {
+        if (DEBUG) Log.d(TAG, "delete " + uri);
+        return 0;
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        if (DEBUG) Log.d(TAG, "query " + uri);
+        return null;
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, String selection, String[]
+            selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+        if (DEBUG) Log.d(TAG, "query " + uri);
+        return null;
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, Bundle queryArgs,
+            CancellationSignal cancellationSignal) {
+        if (DEBUG) Log.d(TAG, "query " + uri);
+        return null;
+    }
+
+    @Override
+    public final Uri insert(Uri uri, ContentValues values) {
+        if (DEBUG) Log.d(TAG, "insert " + uri);
+        return null;
+    }
+
+    @Override
+    public final String getType(Uri uri) {
+        if (DEBUG) Log.d(TAG, "getFormat " + uri);
+        return SLICE_TYPE;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        if (method.equals(METHOD_SLICE)) {
+            Uri uri = extras.getParcelable(EXTRA_BIND_URI);
+            if (Binder.getCallingUid() != Process.myUid()) {
+                getContext().enforceUriPermission(uri, permission.BIND_SLICE,
+                        permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
+                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                        "Slice binding requires the permission BIND_SLICE");
+            }
+            List<SliceSpec> specs = getSpecs(extras);
+
+            Slice s = handleBindSlice(uri, specs);
+            Bundle b = new Bundle();
+            b.putParcelable(EXTRA_SLICE, s.toBundle());
+            return b;
+        } else if (method.equals(METHOD_MAP_INTENT)) {
+            if (Binder.getCallingUid() != Process.myUid()) {
+                getContext().enforceCallingPermission(permission.BIND_SLICE,
+                        "Slice binding requires the permission BIND_SLICE");
+            }
+            Intent intent = extras.getParcelable(EXTRA_INTENT);
+            Uri uri = mSliceProvider.onMapIntentToUri(intent);
+            Bundle b = new Bundle();
+            if (uri != null) {
+                List<SliceSpec> specs = getSpecs(extras);
+                Slice s = handleBindSlice(uri, specs);
+                b.putParcelable(EXTRA_SLICE, s.toBundle());
+            } else {
+                b.putParcelable(EXTRA_SLICE, null);
+            }
+            return b;
+        }
+        return super.call(method, arg, extras);
+    }
+
+    private Slice handleBindSlice(final Uri sliceUri, final List<SliceSpec> specs) {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            return onBindSliceStrict(sliceUri, specs);
+        } else {
+            final CountDownLatch latch = new CountDownLatch(1);
+            final Slice[] output = new Slice[1];
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    output[0] = onBindSliceStrict(sliceUri, specs);
+                    latch.countDown();
+                }
+            });
+            try {
+                latch.await();
+                return output[0];
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> specs) {
+        ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+        try {
+            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                    .detectAll()
+                    .penaltyDeath()
+                    .build());
+            SliceProvider.setSpecs(specs);
+            try {
+                return mSliceProvider.onBindSlice(sliceUri);
+            } finally {
+                SliceProvider.setSpecs(null);
+            }
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    /**
+     * Compat version of {@link Slice#bindSlice}.
+     */
+    public static Slice bindSlice(Context context, Uri uri,
+            List<SliceSpec> supportedSpecs) {
+        ContentProviderClient provider = context.getContentResolver()
+                .acquireContentProviderClient(uri);
+        if (provider == null) {
+            throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+        try {
+            Bundle extras = new Bundle();
+            extras.putParcelable(EXTRA_BIND_URI, uri);
+            addSpecs(extras, supportedSpecs);
+            final Bundle res = provider.call(METHOD_SLICE, null, extras);
+            if (res == null) {
+                return null;
+            }
+            Parcelable bundle = res.getParcelable(EXTRA_SLICE);
+            if (!(bundle instanceof Bundle)) {
+                return null;
+            }
+            return new Slice((Bundle) bundle);
+        } catch (RemoteException e) {
+            // Arbitrary and not worth documenting, as Activity
+            // Manager will kill this process shortly anyway.
+            return null;
+        } finally {
+            provider.close();
+        }
+    }
+
+    private static void addSpecs(Bundle extras, List<SliceSpec> supportedSpecs) {
+        ArrayList<String> types = new ArrayList<>();
+        ArrayList<Integer> revs = new ArrayList<>();
+        for (SliceSpec spec : supportedSpecs) {
+            types.add(spec.getType());
+            revs.add(spec.getRevision());
+        }
+        extras.putStringArrayList(EXTRA_SUPPORTED_SPECS, types);
+        extras.putIntegerArrayList(EXTRA_SUPPORTED_SPECS_REVS, revs);
+    }
+
+    private static List<SliceSpec> getSpecs(Bundle extras) {
+        ArrayList<SliceSpec> specs = new ArrayList<>();
+        ArrayList<String> types = extras.getStringArrayList(EXTRA_SUPPORTED_SPECS);
+        ArrayList<Integer> revs = extras.getIntegerArrayList(EXTRA_SUPPORTED_SPECS_REVS);
+        for (int i = 0; i < types.size(); i++) {
+            specs.add(new SliceSpec(types.get(i), revs.get(i)));
+        }
+        return specs;
+    }
+
+    /**
+     * Compat version of {@link Slice#bindSlice}.
+     */
+    public static Slice bindSlice(Context context, Intent intent,
+            List<SliceSpec> supportedSpecs) {
+        ContentResolver resolver = context.getContentResolver();
+
+        // Check if the intent has data for the slice uri on it and use that
+        final Uri intentData = intent.getData();
+        if (intentData != null && SLICE_TYPE.equals(resolver.getType(intentData))) {
+            return bindSlice(context, intentData, supportedSpecs);
+        }
+        // Otherwise ask the app
+        List<ResolveInfo> providers =
+                context.getPackageManager().queryIntentContentProviders(intent, 0);
+        if (providers == null) {
+            throw new IllegalArgumentException("Unable to resolve intent " + intent);
+        }
+        String authority = providers.get(0).providerInfo.authority;
+        Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority).build();
+        ContentProviderClient provider = resolver.acquireContentProviderClient(uri);
+        if (provider == null) {
+            throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+        try {
+            Bundle extras = new Bundle();
+            extras.putParcelable(EXTRA_INTENT, intent);
+            addSpecs(extras, supportedSpecs);
+            final Bundle res = provider.call(METHOD_MAP_INTENT, null, extras);
+            if (res == null) {
+                return null;
+            }
+            Parcelable bundle = res.getParcelable(EXTRA_SLICE);
+            if (!(bundle instanceof Bundle)) {
+                return null;
+            }
+            return new Slice((Bundle) bundle);
+        } catch (RemoteException e) {
+            // Arbitrary and not worth documenting, as Activity
+            // Manager will kill this process shortly anyway.
+            return null;
+        } finally {
+            provider.close();
+        }
+    }
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderWrapper.java b/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderWrapper.java
new file mode 100644
index 0000000..b61d4d8
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderWrapper.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 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.
+ */
+package androidx.app.slice.compat;
+
+import static androidx.app.slice.SliceConvert.wrap;
+
+import android.annotation.TargetApi;
+import android.app.slice.Slice;
+import android.app.slice.SliceProvider;
+import android.app.slice.SliceSpec;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+
+import java.util.List;
+
+import androidx.app.slice.SliceConvert;
+
+/**
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+@TargetApi(28)
+public class SliceProviderWrapper extends SliceProvider {
+
+    private androidx.app.slice.SliceProvider mSliceProvider;
+
+    public SliceProviderWrapper(androidx.app.slice.SliceProvider provider) {
+        mSliceProvider = provider;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return mSliceProvider.onCreateSliceProvider();
+    }
+
+    @Override
+    public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedVersions) {
+        androidx.app.slice.SliceProvider.setSpecs(wrap(supportedVersions));
+        try {
+            return SliceConvert.unwrap(mSliceProvider.onBindSlice(sliceUri));
+        } finally {
+            androidx.app.slice.SliceProvider.setSpecs(null);
+        }
+    }
+
+    /**
+     * Maps intents to uris.
+     */
+    @Override
+    public @NonNull Uri onMapIntentToUri(Intent intent) {
+        return mSliceProvider.onMapIntentToUri(intent);
+    }
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/core/SliceHints.java b/slices/core/src/main/java/androidx/app/slice/core/SliceHints.java
new file mode 100644
index 0000000..c98f1d2
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/core/SliceHints.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.core;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.app.slice.Slice;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Temporary class to contain hint constants for slices to be used.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class SliceHints {
+    /**
+     * Subtype to indicate that this content has a toggle action associated with it. To indicate
+     * that the toggle is on, use {@link Slice#HINT_SELECTED}. When the toggle state changes, the
+     * intent associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE}
+     * which can be retrieved to see the new state of the toggle.
+     */
+    public static final String SUBTYPE_TOGGLE = "toggle";
+
+    /**
+     * Subtype indicating that this content is the maximum value for a slider or progress.
+     */
+    public static final String SUBTYPE_MAX = "max";
+
+    /**
+     * Subtype indicating that this content is the current value for a slider or progress.
+     */
+    public static final String SUBTYPE_PROGRESS = "progress";
+
+    /**
+     * Key to retrieve an extra added to an intent when a control is changed.
+     */
+    public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
+
+    /**
+     * Key to retrieve an extra added to an intent when the value of a slider has changed.
+     */
+    public static final String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE";
+
+    /**
+     * Hint indicating this content should be shown instead of the normal content when the slice
+     * is in small format
+     */
+    public static final String HINT_SUMMARY = "summary";
+
+}
diff --git a/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java b/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java
new file mode 100644
index 0000000..f0f2371
--- /dev/null
+++ b/slices/core/src/main/java/androidx/app/slice/core/SliceQuery.java
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.core;
+
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+
+import android.annotation.TargetApi;
+import android.support.annotation.RestrictTo;
+import android.text.TextUtils;
+
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Queue;
+import java.util.Spliterators;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+
+/**
+ * Utilities for finding content within a Slice.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+// TODO: Not expect 24.
+@TargetApi(24)
+public class SliceQuery {
+
+    /**
+     */
+    public static boolean hasAnyHints(SliceItem item, String... hints) {
+        if (hints == null) return false;
+        List<String> itemHints = item.getHints();
+        for (String hint : hints) {
+            if (itemHints.contains(hint)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     */
+    public static boolean hasHints(SliceItem item, String... hints) {
+        if (hints == null) return true;
+        List<String> itemHints = item.getHints();
+        for (String hint : hints) {
+            if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     */
+    public static boolean hasHints(Slice item, String... hints) {
+        if (hints == null) return true;
+        List<String> itemHints = item.getHints();
+        for (String hint : hints) {
+            if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     */
+    public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
+        SliceItem ret = null;
+        while (ret == null && list.size() != 0) {
+            SliceItem remove = list.remove(0);
+            if (!contains(container, remove)) {
+                ret = remove;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     */
+    private static boolean contains(SliceItem container, final SliceItem item) {
+        if (container == null || item == null) return false;
+        return stream(container).filter(new Predicate<SliceItem>() {
+            @Override
+            public boolean test(SliceItem s) {
+                return s == item;
+            }
+        }).findAny().isPresent();
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(SliceItem s, String format) {
+        return findAll(s, format, (String[]) null, null);
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(Slice s, String format, String hints, String nonHints) {
+        return findAll(s, format, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(SliceItem s, String format, String hints,
+            String nonHints) {
+        return findAll(s, format, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(Slice s, final String format, final String[] hints,
+            final String[] nonHints) {
+        return stream(s).filter(new Predicate<SliceItem>() {
+            @Override
+            public boolean test(SliceItem item) {
+                return checkFormat(item, format)
+                        && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
+            }
+        }).collect(Collectors.<SliceItem>toList());
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(SliceItem s, final String format, final String[] hints,
+            final String[] nonHints) {
+        return stream(s).filter(new Predicate<SliceItem>() {
+            @Override
+            public boolean test(SliceItem item) {
+                return checkFormat(item, format)
+                        && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
+            }
+        }).collect(Collectors.<SliceItem>toList());
+    }
+
+    /**
+     */
+    public static SliceItem find(Slice s, String format, String hints, String nonHints) {
+        return find(s, format, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static SliceItem find(Slice s, String format) {
+        return find(s, format, (String[]) null, null);
+    }
+
+    /**
+     */
+    public static SliceItem find(SliceItem s, String format) {
+        return find(s, format, (String[]) null, null);
+    }
+
+    /**
+     */
+    public static SliceItem find(SliceItem s, String format, String hints, String nonHints) {
+        return find(s, format, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static SliceItem find(Slice s, final String format, final String[] hints,
+            final String[] nonHints) {
+        return stream(s).filter(new Predicate<SliceItem>() {
+            @Override
+            public boolean test(SliceItem item) {
+                return checkFormat(item, format)
+                        && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
+            }
+        }).findFirst().orElse(null);
+    }
+
+    /**
+     */
+    public static SliceItem findSubtype(Slice s, final String format, final String subtype) {
+        return stream(s).filter(new Predicate<SliceItem>() {
+            @Override
+            public boolean test(SliceItem item) {
+                return checkFormat(item, format) && checkSubtype(item, subtype);
+            }
+        }).findFirst().orElse(null);
+    }
+
+    /**
+     */
+    public static SliceItem findSubtype(SliceItem s, final String format, final String subtype) {
+        return stream(s).filter(new Predicate<SliceItem>() {
+            @Override
+            public boolean test(SliceItem item) {
+                return checkFormat(item, format) && checkSubtype(item, subtype);
+            }
+        }).findFirst().orElse(null);
+    }
+
+    /**
+     */
+    public static SliceItem find(SliceItem s, final String format, final String[] hints,
+            final String[] nonHints) {
+        return stream(s).filter(new Predicate<SliceItem>() {
+            @Override
+            public boolean test(SliceItem item) {
+                return checkFormat(item, format)
+                        && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
+            }
+        }).findFirst().orElse(null);
+    }
+
+    private static boolean checkFormat(SliceItem item, String format) {
+        return format == null || format.equals(item.getFormat());
+    }
+
+    private static boolean checkSubtype(SliceItem item, String subtype) {
+        return subtype == null || subtype.equals(item.getSubType());
+    }
+
+    /**
+     */
+    public static Stream<SliceItem> stream(SliceItem slice) {
+        Queue<SliceItem> items = new ArrayDeque<>();
+        items.add(slice);
+        return getSliceItemStream(items);
+    }
+
+    /**
+     */
+    public static Stream<SliceItem> stream(Slice slice) {
+        Queue<SliceItem> items = new ArrayDeque<>();
+        items.addAll(slice.getItems());
+        return getSliceItemStream(items);
+    }
+
+    /**
+     */
+    private static Stream<SliceItem> getSliceItemStream(final Queue<SliceItem> items) {
+        Iterator<SliceItem> iterator = new Iterator<SliceItem>() {
+            @Override
+            public boolean hasNext() {
+                return items.size() != 0;
+            }
+
+            @Override
+            public SliceItem next() {
+                SliceItem item = items.poll();
+                if (FORMAT_SLICE.equals(item.getFormat())
+                        || FORMAT_ACTION.equals(item.getFormat())) {
+                    items.addAll(item.getSlice().getItems());
+                }
+                return item;
+            }
+        };
+        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
+    }
+}
diff --git a/slices/core/src/main/res-public/values-v28/strings.xml b/slices/core/src/main/res-public/values-v28/strings.xml
new file mode 100644
index 0000000..12dabc6
--- /dev/null
+++ b/slices/core/src/main/res-public/values-v28/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="slice_provider">androidx.app.slice.compat.SliceProviderWrapper</string>
+</resources>
\ No newline at end of file
diff --git a/slices/core/src/main/res-public/values/strings.xml b/slices/core/src/main/res-public/values/strings.xml
new file mode 100644
index 0000000..d492a38
--- /dev/null
+++ b/slices/core/src/main/res-public/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="slice_provider">androidx.app.slice.compat.SliceProviderCompat</string>
+</resources>
diff --git a/slices/view/Android.mk b/slices/view/Android.mk
new file mode 100644
index 0000000..6c487d0
--- /dev/null
+++ b/slices/view/Android.mk
@@ -0,0 +1,48 @@
+# Copyright (C) 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-slices-core \
+#       android-slices-view
+#
+# in their makefiles to include the resources and their dependencies in their package.
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := android-slices-view
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under,src/main/java) \
+    $(call all-Iaidl-files-under,src/main/java)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/src/main/res
+
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations \
+    android-support-v7-recyclerview \
+    android-support-v4 \
+    android-support-compat \
+    android-slices-core \
+    apptoolkit-lifecycle-extensions \
+    apptoolkit-arch-core-runtime
+
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v7-appcompat \
+
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/slices/view/api/current.txt b/slices/view/api/current.txt
new file mode 100644
index 0000000..e958f22
--- /dev/null
+++ b/slices/view/api/current.txt
@@ -0,0 +1,43 @@
+package androidx.app.slice {
+
+  public class SliceUtils {
+    method public static androidx.app.slice.Slice parseSlice(java.io.InputStream, java.lang.String) throws java.io.IOException;
+    method public static void serializeSlice(androidx.app.slice.Slice, android.content.Context, java.io.OutputStream, java.lang.String, androidx.app.slice.SliceUtils.SerializeOptions) throws java.io.IOException;
+  }
+
+  public static class SliceUtils.SerializeOptions {
+    ctor public SliceUtils.SerializeOptions();
+    method public androidx.app.slice.SliceUtils.SerializeOptions setActionMode(int);
+    method public androidx.app.slice.SliceUtils.SerializeOptions setImageMode(int);
+    field public static final int MODE_DISABLE = 2; // 0x2
+    field public static final int MODE_REMOVE = 1; // 0x1
+    field public static final int MODE_THROW = 0; // 0x0
+  }
+
+}
+
+package androidx.app.slice.widget {
+
+  public final class SliceLiveData {
+    ctor public SliceLiveData();
+    method public static android.arch.lifecycle.LiveData<androidx.app.slice.Slice> fromIntent(android.content.Context, android.content.Intent);
+    method public static android.arch.lifecycle.LiveData<androidx.app.slice.Slice> fromUri(android.content.Context, android.net.Uri);
+  }
+
+  public class SliceView extends android.view.ViewGroup implements android.arch.lifecycle.Observer {
+    ctor public SliceView(android.content.Context);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
+    method public int getMode();
+    method public void onChanged(androidx.app.slice.Slice);
+    method public void setMode(int);
+    method public void setScrollable(boolean);
+    method public void setSlice(androidx.app.slice.Slice);
+    field public static final int MODE_LARGE = 2; // 0x2
+    field public static final int MODE_SHORTCUT = 3; // 0x3
+    field public static final int MODE_SMALL = 1; // 0x1
+  }
+
+}
+
diff --git a/slices/view/build.gradle b/slices/view/build.gradle
new file mode 100644
index 0000000..8d87eb5
--- /dev/null
+++ b/slices/view/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryVersions
+import android.support.LibraryGroups
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    implementation(project(":slices-core"))
+    implementation(project(":slices-builders"))
+    implementation(project(":recyclerview-v7"))
+    api(ARCH_LIFECYCLE_EXTENSIONS, libs.exclude_annotations_transitive)
+
+    androidTestImplementation(TEST_RUNNER)
+}
+
+supportLibrary {
+    name = "Slice views"
+    publish = true
+    mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+    mavenGroup = LibraryGroups.SLICES
+    inceptionYear = "2017"
+    description = "A library that handles rendering of slice content into supported templates"
+    minSdkVersion = 24
+}
diff --git a/slices/view/lint-baseline.xml b/slices/view/lint-baseline.xml
new file mode 100644
index 0000000..49d372e
--- /dev/null
+++ b/slices/view/lint-baseline.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0">
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: SliceView.MODE_SMALL, SliceView.MODE_LARGE, SliceView.MODE_SHORTCUT"
+        errorLine1="        return mMode;"
+        errorLine2="               ~~~~~">
+        <location
+            file="src/main/java/androidx/app/slice/widget/SliceView.java"
+            line="290"
+            column="16"/>
+    </issue>
+
+</issues>
diff --git a/slices/view/src/androidTest/AndroidManifest.xml b/slices/view/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..b656032
--- /dev/null
+++ b/slices/view/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.app.slice.view.test">
+    <uses-sdk android:minSdkVersion="28"/>
+</manifest>
diff --git a/slices/view/src/androidTest/NO_DOCS b/slices/view/src/androidTest/NO_DOCS
new file mode 100644
index 0000000..4dad694
--- /dev/null
+++ b/slices/view/src/androidTest/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/slices/view/src/androidTest/java/androidx/app/slice/SliceXmlTest.java b/slices/view/src/androidTest/java/androidx/app/slice/SliceXmlTest.java
new file mode 100644
index 0000000..5e4444d
--- /dev/null
+++ b/slices/view/src/androidTest/java/androidx/app/slice/SliceXmlTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice;
+
+
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SliceXmlTest {
+
+    private final Context mContext = InstrumentationRegistry.getContext();
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testThrowForAction() throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        Slice s = new Slice.Builder(Uri.parse("content://pkg/slice"))
+                .addAction(null, null, null)
+                .build();
+        SliceUtils.serializeSlice(s, mContext, outputStream, "UTF-8", new SliceUtils
+                .SerializeOptions());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testThrowForRemoteInput() throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        Slice s = new Slice.Builder(Uri.parse("content://pkg/slice"))
+                .addRemoteInput(null, null)
+                .build();
+        SliceUtils.serializeSlice(s, mContext, outputStream, "UTF-8", new SliceUtils
+                .SerializeOptions());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testThrowForImage() throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        Slice s = new Slice.Builder(Uri.parse("content://pkg/slice"))
+                .addIcon(null, null)
+                .build();
+        SliceUtils.serializeSlice(s, mContext, outputStream, "UTF-8", new SliceUtils
+                .SerializeOptions());
+    }
+
+    @Test
+    public void testNoThrowForAction() throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        Slice s = new Slice.Builder(Uri.parse("content://pkg/slice"))
+                .addAction(null, null, null)
+                .build();
+        SliceUtils.serializeSlice(s, mContext, outputStream, "UTF-8", new SliceUtils
+                .SerializeOptions().setActionMode(SliceUtils.SerializeOptions.MODE_REMOVE));
+    }
+
+    @Test
+    public void testNoThrowForRemoteInput() throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        Slice s = new Slice.Builder(Uri.parse("content://pkg/slice"))
+                .addRemoteInput(null, null)
+                .build();
+        SliceUtils.serializeSlice(s, mContext, outputStream, "UTF-8", new SliceUtils
+                .SerializeOptions().setActionMode(SliceUtils.SerializeOptions.MODE_REMOVE));
+    }
+
+    @Test
+    public void testNoThrowForImage() throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        Slice s = new Slice.Builder(Uri.parse("content://pkg/slice"))
+                .addIcon(null, null)
+                .build();
+        SliceUtils.serializeSlice(s, mContext, outputStream, "UTF-8", new SliceUtils
+                .SerializeOptions().setImageMode(SliceUtils.SerializeOptions.MODE_REMOVE));
+    }
+
+    @Test
+    public void testSerialization() throws IOException {
+        Bitmap b = Bitmap.createBitmap(50, 25, Bitmap.Config.ARGB_8888);
+        new Canvas(b).drawColor(0xffff0000);
+        // Create a slice containing all the types in a hierarchy.
+        Slice before = new Slice.Builder(Uri.parse("content://pkg/slice"))
+                .addSubSlice(new Slice.Builder(Uri.parse("content://pkg/slice/sub"))
+                        .addTimestamp(System.currentTimeMillis(), null, "Hint")
+                        .build())
+                .addIcon(Icon.createWithBitmap(b), null)
+                .addText("Some text", null)
+                .addAction(null, new Slice.Builder(Uri.parse("content://pkg/slice/sub"))
+                        .addText("Action text", null)
+                        .build(), null)
+                .addInt(0xff00ff00, "subtype")
+                .addHints("Hint 1", "Hint 2")
+                .build();
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        SliceUtils.serializeSlice(before, mContext, outputStream, "UTF-8",
+                new SliceUtils.SerializeOptions()
+                        .setImageMode(SliceUtils.SerializeOptions.MODE_DISABLE)
+                        .setActionMode(SliceUtils.SerializeOptions.MODE_DISABLE));
+
+        byte[] bytes = outputStream.toByteArray();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+        Slice after = SliceUtils.parseSlice(inputStream, "UTF-8");
+
+        assertEquivalent(before, after);
+    }
+
+    private void assertEquivalent(Slice desired, Slice actual) {
+        assertEquals(desired.getUri(), actual.getUri());
+        assertEquals(desired.getHints(), actual.getHints());
+        assertEquals(desired.getItems().size(), actual.getItems().size());
+
+        for (int i = 0; i < desired.getItems().size(); i++) {
+            assertEquivalent(desired.getItems().get(i), actual.getItems().get(i));
+        }
+    }
+
+    private void assertEquivalent(SliceItem desired, SliceItem actual) {
+        boolean isSliceType = FORMAT_SLICE.equals(desired.getFormat())
+                || FORMAT_ACTION.equals(desired.getFormat());
+        if (isSliceType) {
+            assertTrue(FORMAT_SLICE.equals(actual.getFormat())
+                    || FORMAT_ACTION.equals(actual.getFormat()));
+        } else {
+            assertEquals(desired.getFormat(), actual.getFormat());
+            if (FORMAT_TEXT.equals(desired.getFormat())) {
+                assertEquals(String.valueOf(desired.getText()), String.valueOf(actual.getText()));
+            }
+        }
+    }
+}
diff --git a/slices/view/src/main/AndroidManifest.xml b/slices/view/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5e9dd17
--- /dev/null
+++ b/slices/view/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.app.slice.view">
+    <uses-sdk android:minSdkVersion="28"/>
+</manifest>
diff --git a/slices/view/src/main/java/androidx/app/slice/SliceUtils.java b/slices/view/src/main/java/androidx/app/slice/SliceUtils.java
new file mode 100644
index 0000000..ededbfd
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/SliceUtils.java
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice;
+
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Utilities for dealing with slices.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SliceUtils {
+
+    private SliceUtils() {
+    }
+
+    /**
+     * Serialize a slice to an OutputStream.
+     * <p>
+     * The slice can later be read into slice form again with {@link #parseSlice}.
+     * Some slice types cannot be serialized, their handling is controlled by
+     * {@link SerializeOptions}.
+     *
+     * @param s The slice to serialize.
+     * @param context Context used to load any resources in the slice.
+     * @param output The output of the serialization.
+     * @param encoding The encoding to use for serialization.
+     * @param options Options defining how to handle non-serializable items.
+     */
+    public static void serializeSlice(@NonNull Slice s, @NonNull Context context,
+            @NonNull OutputStream output, @NonNull String encoding,
+            @NonNull SerializeOptions options) throws IOException {
+        SliceXml.serializeSlice(s, context, output, encoding, options);
+    }
+
+    /**
+     * Parse a slice that has been previously serialized.
+     * <p>
+     * Parses a slice that was serialized with {@link #serializeSlice}.
+     *
+     * @param input The input stream to read from.
+     * @param encoding The encoding to read as.
+     */
+    public static @NonNull Slice parseSlice(@NonNull InputStream input, @NonNull String encoding)
+            throws IOException {
+        return SliceXml.parseSlice(input, encoding);
+    }
+
+    /**
+     * Holds options for how to handle SliceItems that cannot be serialized.
+     */
+    public static class SerializeOptions {
+        /**
+         * Constant indicating that the an {@link IllegalArgumentException} should be thrown
+         * when this format is encountered.
+         */
+        public static final int MODE_THROW = 0;
+        /**
+         * Constant indicating that the SliceItem should be removed when this format is encountered.
+         */
+        public static final int MODE_REMOVE = 1;
+        /**
+         * Constant indicating that the SliceItem should be serialized as much as possible.
+         * <p>
+         * For images this means it will be replaced with an empty image. For actions, the
+         * action will be removed but the content of the action will be serialized.
+         */
+        public static final int MODE_DISABLE = 2;
+
+        @IntDef({MODE_THROW, MODE_REMOVE, MODE_DISABLE})
+        @interface FormatMode {
+        }
+
+        private int mActionMode = MODE_THROW;
+        private int mImageMode = MODE_THROW;
+
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public void checkThrow(String format) {
+            switch (format) {
+                case FORMAT_ACTION:
+                case FORMAT_REMOTE_INPUT:
+                    if (mActionMode != MODE_THROW) return;
+                    break;
+                case FORMAT_IMAGE:
+                    if (mImageMode != MODE_THROW) return;
+                    break;
+                default:
+                    return;
+            }
+            throw new IllegalArgumentException(format + " cannot be serialized");
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public @FormatMode int getActionMode() {
+            return mActionMode;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public @FormatMode int getImageMode() {
+            return mImageMode;
+        }
+
+        /**
+         * Sets how {@link android.app.slice.SliceItem#FORMAT_ACTION} items should be handled.
+         *
+         * The default mode is {@link #MODE_THROW}.
+         * @param mode The desired mode.
+         */
+        public SerializeOptions setActionMode(@FormatMode int mode) {
+            mActionMode = mode;
+            return this;
+        }
+
+        /**
+         * Sets how {@link android.app.slice.SliceItem#FORMAT_IMAGE} items should be handled.
+         *
+         * The default mode is {@link #MODE_THROW}.
+         * @param mode The desired mode.
+         */
+        public SerializeOptions setImageMode(@FormatMode int mode) {
+            mImageMode = mode;
+            return this;
+        }
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/SliceXml.java b/slices/view/src/main/java/androidx/app/slice/SliceXml.java
new file mode 100644
index 0000000..2500ef6
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/SliceXml.java
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice;
+
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.RestrictTo;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class SliceXml {
+
+    private static final String NAMESPACE = null;
+
+    private static final String TAG_SLICE = "slice";
+    private static final String TAG_ITEM = "item";
+
+    private static final String ATTR_URI = "uri";
+    private static final String ATTR_FORMAT = "format";
+    private static final String ATTR_SUBTYPE = "subtype";
+    private static final String ATTR_HINTS = "hints";
+
+    public static Slice parseSlice(InputStream input, String encoding) throws IOException {
+        try {
+            XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+            parser.setInput(input, encoding);
+
+            int outerDepth = parser.getDepth();
+            int type;
+            Slice s = null;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type != START_TAG) {
+                    continue;
+                }
+                s = parseSlice(parser);
+            }
+            return s;
+        } catch (XmlPullParserException e) {
+            throw new IOException("Unable to init XML Serialization", e);
+        }
+    }
+
+    @SuppressLint("WrongConstant")
+    private static Slice parseSlice(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        if (!TAG_SLICE.equals(parser.getName())) {
+            throw new IOException("Unexpected tag " + parser.getName());
+        }
+        int outerDepth = parser.getDepth();
+        int type;
+        String uri = parser.getAttributeValue(NAMESPACE, ATTR_URI);
+        Slice.Builder b = new Slice.Builder(Uri.parse(uri));
+        String[] hints = hints(parser.getAttributeValue(NAMESPACE, ATTR_HINTS));
+        b.addHints(hints);
+
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == START_TAG && TAG_ITEM.equals(parser.getName())) {
+                parseItem(b, parser);
+            }
+        }
+        return b.build();
+    }
+
+    @SuppressLint("WrongConstant")
+    private static void parseItem(Slice.Builder b, XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int type;
+        int outerDepth = parser.getDepth();
+        String format = parser.getAttributeValue(NAMESPACE, ATTR_FORMAT);
+        String subtype = parser.getAttributeValue(NAMESPACE, ATTR_SUBTYPE);
+        String hintStr = parser.getAttributeValue(NAMESPACE, ATTR_HINTS);
+        String[] hints = hints(hintStr);
+        String v;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == TEXT) {
+                switch (format) {
+                    case android.app.slice.SliceItem.FORMAT_REMOTE_INPUT:
+                        // Nothing for now.
+                        break;
+                    case android.app.slice.SliceItem.FORMAT_IMAGE:
+                        v = parser.getText();
+                        if (!TextUtils.isEmpty(v)) {
+                            if (android.os.Build.VERSION.SDK_INT
+                                    >= android.os.Build.VERSION_CODES.M) {
+                                String[] split = v.split(",");
+                                int w = Integer.parseInt(split[0]);
+                                int h = Integer.parseInt(split[1]);
+                                Bitmap image = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
+                                b.addIcon(Icon.createWithBitmap(image), subtype, hints);
+                            }
+                        }
+                        break;
+                    case android.app.slice.SliceItem.FORMAT_INT:
+                        v = parser.getText();
+                        b.addInt(Integer.parseInt(v), subtype, hints);
+                        break;
+                    case android.app.slice.SliceItem.FORMAT_TEXT:
+                        v = parser.getText();
+                        b.addText(Html.fromHtml(v), subtype, hints);
+                        break;
+                    case android.app.slice.SliceItem.FORMAT_TIMESTAMP:
+                        v = parser.getText();
+                        b.addTimestamp(Long.parseLong(v), subtype, hints);
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unrecognized format " + format);
+                }
+            } else if (type == START_TAG && TAG_SLICE.equals(parser.getName())) {
+                b.addSubSlice(parseSlice(parser), subtype);
+            }
+        }
+    }
+
+    private static String[] hints(String hintStr) {
+        return TextUtils.isEmpty(hintStr) ? new String[0] : hintStr.split(",");
+    }
+
+    public static void serializeSlice(Slice s, Context context, OutputStream output,
+            String encoding, SliceUtils.SerializeOptions options) throws IOException {
+        try {
+            XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
+            serializer.setOutput(output, encoding);
+            serializer.startDocument(encoding, null);
+
+            serialize(s, context, options, serializer);
+
+            serializer.endDocument();
+            serializer.flush();
+        } catch (XmlPullParserException e) {
+            throw new IOException("Unable to init XML Serialization", e);
+        }
+    }
+
+    private static void serialize(Slice s, Context context, SliceUtils.SerializeOptions options,
+            XmlSerializer serializer) throws IOException {
+        serializer.startTag(NAMESPACE, TAG_SLICE);
+        serializer.attribute(NAMESPACE, ATTR_URI, s.getUri().toString());
+        if (!s.getHints().isEmpty()) {
+            serializer.attribute(NAMESPACE, ATTR_HINTS, hintStr(s.getHints()));
+        }
+        for (SliceItem item : s.getItems()) {
+            serialize(item, context, options, serializer);
+        }
+
+        serializer.endTag(NAMESPACE, TAG_SLICE);
+    }
+
+    private static void serialize(SliceItem item, Context context,
+            SliceUtils.SerializeOptions options, XmlSerializer serializer) throws IOException {
+        String format = item.getFormat();
+        options.checkThrow(format);
+
+        serializer.startTag(NAMESPACE, TAG_ITEM);
+        serializer.attribute(NAMESPACE, ATTR_FORMAT, format);
+        if (item.getSubType() != null) {
+            serializer.attribute(NAMESPACE, ATTR_SUBTYPE, item.getSubType());
+        }
+        if (!item.getHints().isEmpty()) {
+            serializer.attribute(NAMESPACE, ATTR_HINTS, hintStr(item.getHints()));
+        }
+
+        switch (format) {
+            case android.app.slice.SliceItem.FORMAT_ACTION:
+                if (options.getActionMode() == SliceUtils.SerializeOptions.MODE_DISABLE) {
+                    serialize(item.getSlice(), context, options, serializer);
+                }
+                break;
+            case android.app.slice.SliceItem.FORMAT_REMOTE_INPUT:
+                // Nothing for now.
+                break;
+            case android.app.slice.SliceItem.FORMAT_IMAGE:
+                if (options.getImageMode() == SliceUtils.SerializeOptions.MODE_DISABLE) {
+                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+                        Drawable d = item.getIcon().loadDrawable(context);
+                        serializer.text(String.format("%d,%d",
+                                d.getIntrinsicWidth(), d.getIntrinsicHeight()));
+                    }
+                }
+                break;
+            case android.app.slice.SliceItem.FORMAT_INT:
+                serializer.text(String.valueOf(item.getInt()));
+                break;
+            case android.app.slice.SliceItem.FORMAT_SLICE:
+                serialize(item.getSlice(), context, options, serializer);
+                break;
+            case android.app.slice.SliceItem.FORMAT_TEXT:
+                if (item.getText() instanceof Spanned) {
+                    serializer.text(Html.toHtml((Spanned) item.getText()));
+                } else {
+                    serializer.text(String.valueOf(item.getText()));
+                }
+                break;
+            case android.app.slice.SliceItem.FORMAT_TIMESTAMP:
+                serializer.text(String.valueOf(item.getTimestamp()));
+                break;
+            default:
+                throw new IllegalArgumentException("Unrecognized format " + format);
+        }
+        serializer.endTag(NAMESPACE, TAG_ITEM);
+    }
+
+    private static String hintStr(List<String> hints) {
+        return TextUtils.join(",", hints);
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/ActionRow.java b/slices/view/src/main/java/androidx/app/slice/widget/ActionRow.java
new file mode 100644
index 0000000..2eaa059
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/ActionRow.java
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.os.AsyncTask;
+import android.support.annotation.RestrictTo;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.function.Consumer;
+
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
+public class ActionRow extends FrameLayout {
+
+    private static final int MAX_ACTIONS = 5;
+    private final int mSize;
+    private final int mIconPadding;
+    private final LinearLayout mActionsGroup;
+    private final boolean mFullActions;
+    private int mColor = Color.BLACK;
+
+    public ActionRow(Context context, boolean fullActions) {
+        super(context);
+        mFullActions = fullActions;
+        mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
+                context.getResources().getDisplayMetrics());
+        mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
+                context.getResources().getDisplayMetrics());
+        mActionsGroup = new LinearLayout(context);
+        mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
+        mActionsGroup.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        addView(mActionsGroup);
+    }
+
+    private void setColor(int color) {
+        mColor = color;
+        for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+            View view = mActionsGroup.getChildAt(i);
+            SliceItem item = (SliceItem) view.getTag();
+            boolean tint = !item.hasHint(HINT_NO_TINT);
+            if (tint) {
+                ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
+            }
+        }
+    }
+
+    private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
+        ImageView imageView = new ImageView(getContext());
+        imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+        imageView.setScaleType(ScaleType.FIT_CENTER);
+        imageView.setImageIcon(icon);
+        if (allowTint) {
+            imageView.setImageTintList(ColorStateList.valueOf(mColor));
+        }
+        imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
+                android.R.attr.selectableItemBackground));
+        imageView.setTag(image);
+        addAction(imageView);
+        return imageView;
+    }
+
+    /**
+     * Set the actions and color for this action row.
+     */
+    public void setActions(SliceItem actionRow, int color) {
+        removeAllViews();
+        mActionsGroup.removeAllViews();
+        addView(mActionsGroup);
+        if (color != -1) {
+            setColor(color);
+        }
+        SliceQuery.findAll(actionRow, FORMAT_ACTION).forEach(new Consumer<SliceItem>() {
+            @Override
+            public void accept(final SliceItem action) {
+                if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
+                    return;
+                }
+                SliceItem image = SliceQuery.find(action, FORMAT_IMAGE);
+                if (image == null) {
+                    return;
+                }
+                boolean tint = !image.hasHint(HINT_NO_TINT);
+                final SliceItem input = SliceQuery.find(action, FORMAT_REMOTE_INPUT);
+                if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
+                    addAction(image.getIcon(), tint, image).setOnClickListener(
+                            new OnClickListener() {
+                                @Override
+                                public void onClick(View v) {
+                                    handleRemoteInputClick(v, action.getAction(),
+                                            input.getRemoteInput());
+                                }
+                            });
+                    createRemoteInputView(mColor, getContext());
+                } else {
+                    addAction(image.getIcon(), tint, image).setOnClickListener(
+                            new OnClickListener() {
+                                @Override
+                                public void onClick(View v) {
+                                    AsyncTask.execute(new Runnable() {
+                                        @Override
+                                        public void run() {
+
+                                            try {
+                                                action.getAction().send();
+                                            } catch (CanceledException e) {
+                                                e.printStackTrace();
+                                            }
+                                        }
+                                    });
+                                }
+                            });
+                }
+            }
+        });
+        setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
+    }
+
+    private void addAction(View child) {
+        mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
+    }
+
+    private void createRemoteInputView(int color, Context context) {
+        View riv = RemoteInputView.inflate(context, this);
+        riv.setVisibility(View.INVISIBLE);
+        addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        riv.setBackgroundColor(color);
+    }
+
+    private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
+            RemoteInput input) {
+        if (input == null) {
+            return false;
+        }
+
+        ViewParent p = view.getParent().getParent();
+        RemoteInputView riv = null;
+        while (p != null) {
+            if (p instanceof View) {
+                View pv = (View) p;
+                riv = findRemoteInputView(pv);
+                if (riv != null) {
+                    break;
+                }
+            }
+            p = p.getParent();
+        }
+        if (riv == null) {
+            return false;
+        }
+
+        int width = view.getWidth();
+        if (view instanceof TextView) {
+            // Center the reveal on the text which might be off-center from the TextView
+            TextView tv = (TextView) view;
+            if (tv.getLayout() != null) {
+                int innerWidth = (int) tv.getLayout().getLineWidth(0);
+                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+                width = Math.min(width, innerWidth);
+            }
+        }
+        int cx = view.getLeft() + width / 2;
+        int cy = view.getTop() + view.getHeight() / 2;
+        int w = riv.getWidth();
+        int h = riv.getHeight();
+        int r = Math.max(
+                Math.max(cx + cy, cx + (h - cy)),
+                Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+        riv.setRevealParameters(cx, cy, r);
+        riv.setPendingIntent(pendingIntent);
+        riv.setRemoteInput(new RemoteInput[] {
+                input
+        }, input);
+        riv.focusAnimated();
+        return true;
+    }
+
+    private RemoteInputView findRemoteInputView(View v) {
+        if (v == null) {
+            return null;
+        }
+        return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/EventInfo.java b/slices/view/src/main/java/androidx/app/slice/widget/EventInfo.java
new file mode 100644
index 0000000..375ea09
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/EventInfo.java
@@ -0,0 +1,270 @@
+/*
+ * 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.app.slice.widget;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Represents information associated with a logged event on {@link SliceView}.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class EventInfo {
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+            ROW_TYPE_SHORTCUT, ROW_TYPE_LIST, ROW_TYPE_GRID, ROW_TYPE_MESSAGING
+    })
+    public @interface SliceRowType {}
+
+    /**
+     * Indicates the slice is represented as a shortcut.
+     */
+    public static final int ROW_TYPE_SHORTCUT = -1;
+    /**
+     * Indicates the row is represented in a list template.
+     */
+    public static final int ROW_TYPE_LIST = 0;
+    /**
+     * Indicates the row is represented in a grid template.
+     */
+    public static final int ROW_TYPE_GRID = 1;
+    /**
+     * Indicates the row is represented as a messaging template.
+     */
+    public static final int ROW_TYPE_MESSAGING = 2;
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+            ACTION_TYPE_TOGGLE, ACTION_TYPE_BUTTON, ACTION_TYPE_SLIDER, ACTION_TYPE_CONTENT
+    })
+    public @interface SliceActionType{}
+
+    /**
+     * Indicates the event was an interaction with a toggle. Check {@link EventInfo#state} to
+     * see the new state of the toggle.
+     */
+    public static final int ACTION_TYPE_TOGGLE = 0;
+    /**
+     * Indicates the event was an interaction with a button. Check {@link EventInfo#actionPosition}
+     * to see where on the card the button is placed.
+     */
+    public static final int ACTION_TYPE_BUTTON = 1;
+    /**
+     * Indicates the event was an interaction with a slider. Check {@link EventInfo#state} to
+     * see the new state of the slider.
+     */
+    public static final int ACTION_TYPE_SLIDER = 2;
+    /**
+     * Indicates the event was a tap on the entire row.
+     */
+    public static final int ACTION_TYPE_CONTENT = 3;
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+            POSITION_START, POSITION_END, POSITION_CELL
+    })
+    public @interface SliceButtonPosition{}
+
+    /**
+     * Indicates the event was an interaction with a button positioned at the start of the row.
+     */
+    public static final int POSITION_START = 0;
+    /**
+     * Indicates the event was an interaction with a button positioned at the end of the row,
+     * potentially grouped with other buttons.
+     */
+    public static final int POSITION_END = 1;
+    /**
+     * Indicates the event was an interaction with a button positioned in a grid cell.
+     */
+    public static final int POSITION_CELL = 2;
+
+    /**
+     * Indicates the state of a toggle is off.
+     */
+    public static final int STATE_OFF = 0;
+    /**
+     * Indicates the state of a toggle is on.
+     */
+    public static final int STATE_ON = 1;
+
+    /**
+     * The display mode of the slice being interacted with.
+     */
+    public @SliceView.SliceMode int sliceMode;
+    /**
+     * The type of action that occurred.
+     */
+    public @SliceActionType int actionType;
+    /**
+     * The template type of the row that was interacted with in the slice.
+     */
+    public @SliceRowType int rowTemplateType;
+    /**
+     * Index of the row that was interacted with in the slice.
+     */
+    public int rowIndex;
+    /**
+     * If multiple buttons are presented in this {@link #actionPosition} on the row, then this is
+     * the index of that button that was interacted with. For total number of actions
+     * see {@link #actionCount}.
+     *
+     * <p>If the {@link #actionPosition} is {@link #POSITION_CELL} the button is a cell within
+     * a grid, and this index would represent the cell position.</p>
+     * <p>If the {@link #actionPosition} is {@link #POSITION_END} there might be other buttons
+     * in the end position, and this index would represent the position.</p>
+     */
+    public int actionIndex;
+    /**
+     * Total number of actions available in this row of the slice.
+     *
+     * <p>If the {@link #actionPosition} is {@link #POSITION_CELL} the button is a cell within
+     * a grid row, and this is the number of cells in the row.</p>
+     * <p>If the {@link #actionPosition} is {@link #POSITION_END} this is the number of buttons
+     * in the end position of this row.</p>
+     */
+    public int actionCount;
+    /**
+     * Position of the button on the template.
+     *
+     * {@link #POSITION_START}
+     * {@link #POSITION_END}
+     * {@link #POSITION_CELL}
+     */
+    public @SliceButtonPosition int actionPosition;
+    /**
+     * Represents the state after the event or -1 if not applicable for the event type.
+     *
+     * <p>For {@link #ACTION_TYPE_TOGGLE} events, the state will be either {@link #STATE_OFF}
+     * or {@link #STATE_ON}.</p>
+     * <p>For {@link #ACTION_TYPE_SLIDER} events, the state will be a number representing
+     * the new position of the slider.</p>
+     */
+    public int state;
+
+    /**
+     * Constructs an event info object with the required information for an event.
+     *
+     * @param sliceMode The display mode of the slice interacted with.
+     * @param actionType The type of action this event represents.
+     * @param rowTemplateType The template type of the row interacted with.
+     * @param rowIndex The index of the row that was interacted with in the slice.
+     */
+    public EventInfo(@SliceView.SliceMode int sliceMode, @SliceActionType int actionType,
+            @SliceRowType int rowTemplateType, int rowIndex) {
+        this.sliceMode = sliceMode;
+        this.actionType = actionType;
+        this.rowTemplateType = rowTemplateType;
+        this.rowIndex = rowIndex;
+
+        this.actionPosition = -1;
+        this.actionIndex = -1;
+        this.actionCount = -1;
+        this.state = -1;
+    }
+
+    /**
+     * Sets positional information for the event.
+     *
+     * @param actionPosition The position of the button on the template.
+     * @param actionIndex The index of that button that was interacted with.
+     * @param actionCount The number of actions available in this group of buttons on the slice.
+     */
+    public void setPosition(@SliceButtonPosition int actionPosition, int actionIndex,
+            int actionCount) {
+        this.actionPosition = actionPosition;
+        this.actionIndex = actionIndex;
+        this.actionCount = actionCount;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("mode=").append(SliceView.modeToString(sliceMode));
+        sb.append(", actionType=").append(actionToString(actionType));
+        sb.append(", rowTemplateType=").append(rowTypeToString(rowTemplateType));
+        sb.append(", rowIndex=").append(rowIndex);
+        sb.append(", actionPosition=").append(positionToString(actionPosition));
+        sb.append(", actionIndex=").append(actionIndex);
+        sb.append(", actionCount=").append(actionCount);
+        sb.append(", state=").append(state);
+        return sb.toString();
+    }
+
+    /**
+     * @return String representation of the provided position.
+     */
+    private static String positionToString(@SliceButtonPosition int position) {
+        switch (position) {
+            case POSITION_START:
+                return "START";
+            case POSITION_END:
+                return "END";
+            case POSITION_CELL:
+                return "CELL";
+            default:
+                return "unknown position: " + position;
+        }
+    }
+
+    /**
+     * @return String representation of the provided action.
+     */
+    private static String actionToString(@SliceActionType int action) {
+        switch (action) {
+            case ACTION_TYPE_TOGGLE:
+                return "TOGGLE";
+            case ACTION_TYPE_BUTTON:
+                return "BUTTON";
+            case ACTION_TYPE_SLIDER:
+                return "SLIDER";
+            case ACTION_TYPE_CONTENT:
+                return "CONTENT";
+            default:
+                return "unknown action: " + action;
+        }
+    }
+
+    /**
+     * @return String representation of the provided row template type.
+     */
+    private static String rowTypeToString(@SliceRowType int type) {
+        switch (type) {
+            case ROW_TYPE_LIST:
+                return "LIST";
+            case ROW_TYPE_GRID:
+                return "GRID";
+            case ROW_TYPE_MESSAGING:
+                return "MESSAGING";
+            case ROW_TYPE_SHORTCUT:
+                return "SHORTCUT";
+            default:
+                return "unknown row type: " + type;
+        }
+    }
+}
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
new file mode 100644
index 0000000..9569dc0
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/GridContent.java
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import static android.app.slice.Slice.SUBTYPE_COLOR;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+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 static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import android.support.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * Extracts information required to present content in a grid format from a slice.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class GridContent {
+
+    private boolean mAllImages;
+    public SliceItem mColorItem;
+    public ArrayList<CellContent> mGridContent = new ArrayList<>();
+
+    public GridContent(SliceItem gridItem) {
+        populate(gridItem);
+    }
+
+    private void reset() {
+        mColorItem = null;
+        mGridContent.clear();
+    }
+
+    /**
+     * @return whether this grid has content that is valid to display.
+     */
+    public boolean populate(SliceItem gridItem) {
+        reset();
+        mColorItem = SliceQuery.findSubtype(gridItem, FORMAT_INT, SUBTYPE_COLOR);
+        mAllImages = true;
+        if (FORMAT_SLICE.equals(gridItem.getFormat())) {
+            List<SliceItem> items = gridItem.getSlice().getItems();
+            // Check if it it's only one item that is a slice
+            if (items.size() == 1 && items.get(0).getFormat().equals(FORMAT_SLICE)) {
+                items = items.get(0).getSlice().getItems();
+            }
+            for (int i = 0; i < items.size(); i++) {
+                SliceItem item = items.get(i);
+                CellContent cc = new CellContent(item);
+                if (cc.isValid()) {
+                    mGridContent.add(cc);
+                    if (!cc.isImageOnly()) {
+                        mAllImages = false;
+                    }
+                }
+            }
+        } else {
+            CellContent cc = new CellContent(gridItem);
+            if (cc.isValid()) {
+                mGridContent.add(cc);
+            }
+        }
+        return isValid();
+    }
+
+    /**
+     * @return the list of cell content for this grid.
+     */
+    public ArrayList<CellContent> getGridContent() {
+        return mGridContent;
+    }
+
+    /**
+     * @return the color to tint content in this grid.
+     */
+    public SliceItem getColorItem() {
+        return mColorItem;
+    }
+
+    /**
+     * @return whether this grid has content that is valid to display.
+     */
+    public boolean isValid() {
+        return mGridContent.size() > 0;
+    }
+
+    /**
+     * @return whether the contents of this grid is just images.
+     */
+    public boolean isAllImages() {
+        return mAllImages;
+    }
+
+    /**
+     * Extracts information required to present content in a cell.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static class CellContent {
+        private SliceItem mContentIntent;
+        private ArrayList<SliceItem> mCellItems = new ArrayList<>();
+
+        public CellContent(SliceItem cellItem) {
+            populate(cellItem);
+        }
+
+        /**
+         * @return whether this row has content that is valid to display.
+         */
+        public boolean populate(SliceItem cellItem) {
+            final String format = cellItem.getFormat();
+            if (FORMAT_SLICE.equals(format) || FORMAT_ACTION.equals(format)) {
+                List<SliceItem> items = cellItem.getSlice().getItems();
+                // If we've only got one item that's a slice / action use those items instead
+                if (items.size() == 1 && (FORMAT_ACTION.equals(items.get(0).getFormat())
+                        || FORMAT_SLICE.equals(items.get(0).getFormat()))) {
+                    mContentIntent = items.get(0);
+                    items = items.get(0).getSlice().getItems();
+                }
+                if (FORMAT_ACTION.equals(format)) {
+                    mContentIntent = cellItem;
+                }
+                int textCount = 0;
+                int imageCount = 0;
+                for (int i = 0; i < items.size(); i++) {
+                    final SliceItem item = items.get(i);
+                    final String itemFormat = item.getFormat();
+                    if (textCount < 2 && (FORMAT_TEXT.equals(itemFormat)
+                            || FORMAT_TIMESTAMP.equals(itemFormat))) {
+                        textCount++;
+                        mCellItems.add(item);
+                    } else if (imageCount < 1 && FORMAT_IMAGE.equals(item.getFormat())) {
+                        imageCount++;
+                        mCellItems.add(item);
+                    }
+                }
+            } else if (isValidCellContent(cellItem)) {
+                mCellItems.add(cellItem);
+            }
+            return isValid();
+        }
+
+        /**
+         * @return the action to activate when this cell is tapped.
+         */
+        public SliceItem getContentIntent() {
+            return mContentIntent;
+        }
+
+        /**
+         * @return the slice items to display in this cell.
+         */
+        public ArrayList<SliceItem> getCellItems() {
+            return mCellItems;
+        }
+
+        /**
+         * @return whether this is content that is valid to show in a grid cell.
+         */
+        private boolean isValidCellContent(SliceItem cellItem) {
+            final String format = cellItem.getFormat();
+            return FORMAT_TEXT.equals(format)
+                    || FORMAT_TIMESTAMP.equals(format)
+                    || FORMAT_IMAGE.equals(format);
+        }
+
+        /**
+         * @return whether this grid has content that is valid to display.
+         */
+        public boolean isValid() {
+            return mCellItems.size() > 0 && mCellItems.size() <= 3;
+        }
+
+        /**
+         * @return whether this cell contains just an image.
+         */
+        public boolean isImageOnly() {
+            return mCellItems.size() == 1 && FORMAT_IMAGE.equals(mCellItems.get(0).getFormat());
+        }
+    }
+}
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
new file mode 100644
index 0000000..8f74db6
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/GridRowView.java
@@ -0,0 +1,341 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.support.annotation.ColorInt;
+import android.support.annotation.RestrictTo;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Pair;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
+public class GridRowView extends SliceChildView implements View.OnClickListener {
+
+    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;
+
+    // Max number of text items that can show in a cell
+    private static final int MAX_CELL_TEXT = 2;
+    // Max number of text items that can show in a cell if the mode is small
+    private static final int MAX_CELL_TEXT_SMALL = 1;
+    // Max number of images that can show in a cell
+    private static final int MAX_CELL_IMAGES = 1;
+
+    private int mRowIndex;
+    private boolean mIsAllImages;
+    private @SliceView.SliceMode int mSliceMode = 0;
+
+    private int mIconSize;
+    private int mLargeIconSize;
+    private int mBigPictureHeight;
+    private int mAllImagesHeight;
+    private GridContent mGridContent;
+    private LinearLayout mViewContainer;
+
+    public GridRowView(Context context) {
+        this(context, null);
+    }
+
+    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));
+    }
+
+    @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;
+            }
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public int getMode() {
+        return mSliceMode;
+    }
+
+    @Override
+    public void setTint(@ColorInt int tintColor) {
+        super.setTint(tintColor);
+        if (mGridContent != null) {
+            // TODO -- could be smarter about this
+            resetView();
+            populateViews(mGridContent);
+        }
+    }
+
+    /**
+     * This is called when GridView is being used as a small template.
+     */
+    @Override
+    public void setSlice(Slice slice) {
+        mRowIndex = 0;
+        mSliceMode = SliceView.MODE_SMALL;
+        Slice.Builder sb = new Slice.Builder(slice.getUri());
+        sb.addSubSlice(slice);
+        Slice parentSlice = sb.build();
+        mGridContent = new GridContent(parentSlice.getItems().get(0));
+        populateViews(mGridContent);
+    }
+
+    /**
+     * This is called when GridView is being used as a component in a large template.
+     */
+    @Override
+    public void setSliceItem(SliceItem slice, boolean isHeader, int index,
+            SliceView.SliceObserver observer) {
+        setSliceObserver(observer);
+        mRowIndex = index;
+        mSliceMode = SliceView.MODE_LARGE;
+        mGridContent = new GridContent(slice);
+        populateViews(mGridContent);
+    }
+
+    private void populateViews(GridContent gc) {
+        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()) {
+                break;
+            }
+            addCell(cells.get(i), i, Math.min(cells.size(), max));
+        }
+        if (ALLOW_SEE_MORE && mIsAllImages && cells.size() > getChildCount()) {
+            addSeeMoreCount(cells.size() - getChildCount());
+        }
+    }
+
+    private void addSeeMoreCount(int numExtra) {
+        View last = getChildAt(getChildCount() - 1);
+        FrameLayout frame = new FrameLayout(getContext());
+        frame.setLayoutParams(last.getLayoutParams());
+
+        removeView(last);
+        frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        TextView v = new TextView(getContext());
+        v.setTextColor(Color.WHITE);
+        v.setBackgroundColor(0x4d000000);
+        v.setText(getResources().getString(R.string.abc_slice_more_content, numExtra));
+        v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
+        v.setGravity(Gravity.CENTER);
+        frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        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 = mSliceMode == SliceView.MODE_SMALL
+                ? MAX_CELL_TEXT_SMALL
+                : MAX_CELL_TEXT;
+        LinearLayout cellContainer = new LinearLayout(getContext());
+        cellContainer.setOrientation(LinearLayout.VERTICAL);
+        cellContainer.setGravity(Gravity.CENTER_HORIZONTAL);
+
+        ArrayList<SliceItem> cellItems = cell.getCellItems();
+        SliceItem contentIntentItem = cell.getContentIntent();
+
+        int textCount = 0;
+        int imageCount = 0;
+        boolean added = false;
+        boolean singleItem = cellItems.size() == 1;
+        List<SliceItem> textItems = null;
+        // In small format we display one text item and prefer titles
+        if (!singleItem && mSliceMode == SliceView.MODE_SMALL) {
+            // Get all our text items
+            textItems = cellItems.stream().filter(new Predicate<SliceItem>() {
+                @Override
+                public boolean test(SliceItem s) {
+                    return FORMAT_TEXT.equals(s.getFormat());
+                }
+            }).collect(Collectors.<SliceItem>toList());
+            // If we have more than 1 remove non-titles
+            Iterator<SliceItem> iterator = textItems.iterator();
+            while (textItems.size() > 1) {
+                SliceItem item = iterator.next();
+                if (!item.hasHint(HINT_TITLE)) {
+                    iterator.remove();
+                }
+            }
+        }
+        for (int i = 0; i < cellItems.size(); i++) {
+            SliceItem item = cellItems.get(i);
+            final String itemFormat = item.getFormat();
+            if (textCount < maxCellText && (FORMAT_TEXT.equals(itemFormat)
+                    || FORMAT_TIMESTAMP.equals(itemFormat))) {
+                if (textItems != null && !textItems.contains(item)) {
+                    continue;
+                }
+                if (addItem(item, mTintColor, cellContainer, singleItem)) {
+                    textCount++;
+                    added = true;
+                }
+            } else if (imageCount < MAX_CELL_IMAGES && FORMAT_IMAGE.equals(item.getFormat())) {
+                if (addItem(item, mTintColor, cellContainer, singleItem)) {
+                    imageCount++;
+                    added = true;
+                }
+            }
+        }
+        if (added) {
+            mViewContainer.addView(cellContainer,
+                    new LinearLayout.LayoutParams(0, WRAP_CONTENT, 1));
+            if (contentIntentItem != null) {
+                EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_BUTTON,
+                        EventInfo.ROW_TYPE_GRID, mRowIndex);
+                info.setPosition(EventInfo.POSITION_CELL, index, total);
+                Pair<SliceItem, EventInfo> tagItem = new Pair(contentIntentItem, info);
+                cellContainer.setTag(tagItem);
+                makeClickable(cellContainer);
+            }
+        }
+    }
+
+    /**
+     * Adds simple items to a container. Simple items include icons, text, and timestamps.
+     * @return Whether an item was added.
+     */
+    private boolean addItem(SliceItem item, int color, ViewGroup container, boolean singleItem) {
+        final String format = item.getFormat();
+        View addedView = null;
+        if (FORMAT_TEXT.equals(format) || FORMAT_TIMESTAMP.equals(format)) {
+            boolean title = SliceQuery.hasAnyHints(item, HINT_LARGE, HINT_TITLE);
+            TextView tv = (TextView) LayoutInflater.from(getContext()).inflate(title
+                    ? TITLE_TEXT_LAYOUT : TEXT_LAYOUT, null);
+            CharSequence text = FORMAT_TIMESTAMP.equals(format)
+                    ? SliceViewUtil.getRelativeTimeString(item.getTimestamp())
+                    : item.getText();
+            tv.setText(text);
+            container.addView(tv);
+            addedView = tv;
+        } 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;
+            if (item.hasHint(HINT_LARGE)) {
+                iv.setScaleType(ScaleType.CENTER_CROP);
+                size = singleItem ? MATCH_PARENT : mLargeIconSize;
+            }
+            container.addView(iv, new LayoutParams(size, size));
+            addedView = iv;
+        }
+        return addedView != null;
+    }
+
+    private void makeClickable(View layout) {
+        layout.setOnClickListener(this);
+        layout.setBackground(SliceViewUtil.getDrawable(getContext(),
+                android.R.attr.selectableItemBackground));
+    }
+
+    @Override
+    public void onClick(View view) {
+        Pair<SliceItem, EventInfo> tagItem = (Pair<SliceItem, EventInfo>) view.getTag();
+        final SliceItem actionItem = tagItem.first;
+        final EventInfo info = tagItem.second;
+        if (actionItem != null && FORMAT_ACTION.equals(actionItem.getFormat())) {
+            try {
+                actionItem.getAction().send();
+                if (mObserver != null) {
+                    mObserver.onSliceAction(info, actionItem);
+                }
+            } catch (PendingIntent.CanceledException e) {
+                Log.w(TAG, "PendingIntent for slice cannot be sent", e);
+            }
+        }
+    }
+
+    @Override
+    public void resetView() {
+        mIsAllImages = true;
+        mGridContent = null;
+        mViewContainer.removeAllViews();
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/LargeSliceAdapter.java b/slices/view/src/main/java/androidx/app/slice/widget/LargeSliceAdapter.java
new file mode 100644
index 0000000..9888bc7
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/LargeSliceAdapter.java
@@ -0,0 +1,237 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import static android.app.slice.Slice.HINT_HORIZONTAL;
+import static android.app.slice.Slice.SUBTYPE_MESSAGE;
+import static android.app.slice.Slice.SUBTYPE_SOURCE;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_INT;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
+import android.annotation.TargetApi;
+import android.app.slice.Slice;
+import android.content.Context;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
+public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.SliceViewHolder> {
+
+    static final int TYPE_DEFAULT       = 1;
+    static final int TYPE_HEADER        = 2; // TODO: headers shouldn't scroll off
+    static final int TYPE_GRID          = 3;
+    static final int TYPE_MESSAGE       = 4;
+    static final int TYPE_MESSAGE_LOCAL = 5;
+
+    private final IdGenerator mIdGen = new IdGenerator();
+    private final Context mContext;
+    private List<SliceWrapper> mSlices = new ArrayList<>();
+
+    private SliceView.SliceObserver mSliceObserver;
+    private int mColor;
+    private AttributeSet mAttrs;
+
+    public LargeSliceAdapter(Context context) {
+        mContext = context;
+        setHasStableIds(true);
+    }
+
+    public void setSliceObserver(SliceView.SliceObserver observer) {
+        mSliceObserver = observer;
+    }
+
+    /**
+     * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
+     */
+    public void setSliceItems(List<SliceItem> slices, int color) {
+        if (slices == null) {
+            mSlices.clear();
+        } else {
+            mIdGen.resetUsage();
+            mSlices = slices.stream().map(new Function<SliceItem, SliceWrapper>() {
+                @Override
+                public SliceWrapper apply(SliceItem s) {
+                    return new SliceWrapper(s, mIdGen);
+                }
+            }).collect(Collectors.<SliceWrapper>toList());
+        }
+        mColor = color;
+        notifyDataSetChanged();
+    }
+
+    /**
+     * Sets the attribute set to use for views in the list.
+     */
+    public void setStyle(AttributeSet attrs) {
+        mAttrs = attrs;
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View v = inflateForType(viewType);
+        v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        return new SliceViewHolder(v);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mSlices.get(position).mType;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mSlices.get(position).mId;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSlices.size();
+    }
+
+    @Override
+    public void onBindViewHolder(SliceViewHolder holder, int position) {
+        SliceWrapper slice = mSlices.get(position);
+        if (holder.mSliceView != null) {
+            holder.mSliceView.setTint(mColor);
+            holder.mSliceView.setStyle(mAttrs);
+            holder.mSliceView.setSliceItem(slice.mItem, position == 0 /* isHeader */,
+                    position, mSliceObserver);
+        }
+    }
+
+    private View inflateForType(int viewType) {
+        switch (viewType) {
+            case TYPE_GRID:
+                return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_grid, null);
+            case TYPE_MESSAGE:
+                return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message, null);
+            case TYPE_MESSAGE_LOCAL:
+                return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message_local,
+                        null);
+        }
+        return new RowView(mContext);
+    }
+
+    protected static class SliceWrapper {
+        private final SliceItem mItem;
+        private final int mType;
+        private final long mId;
+
+        public SliceWrapper(SliceItem item, IdGenerator idGen) {
+            mItem = item;
+            mType = getFormat(item);
+            mId = idGen.getId(item);
+        }
+
+        public static int getFormat(SliceItem item) {
+            if (SUBTYPE_MESSAGE.equals(item.getSubType())) {
+                // TODO: Better way to determine me or not? Something more like Messaging style.
+                if (SliceQuery.findSubtype(item, null, SUBTYPE_SOURCE) != null) {
+                    return TYPE_MESSAGE;
+                } else {
+                    return TYPE_MESSAGE_LOCAL;
+                }
+            }
+            if (item.hasHint(HINT_HORIZONTAL)) {
+                return TYPE_GRID;
+            }
+            if (!item.hasHint(Slice.HINT_LIST_ITEM)) {
+                return TYPE_HEADER;
+            }
+            return TYPE_DEFAULT;
+        }
+    }
+
+    /**
+     * A {@link RecyclerView.ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
+     */
+    public static class SliceViewHolder extends RecyclerView.ViewHolder {
+        public final SliceChildView mSliceView;
+
+        public SliceViewHolder(View itemView) {
+            super(itemView);
+            mSliceView = itemView instanceof SliceChildView ? (SliceChildView) itemView : null;
+        }
+    }
+
+    private static class IdGenerator {
+        private long mNextLong = 0;
+        private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
+        private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
+
+        public long getId(SliceItem item) {
+            String str = genString(item);
+            if (!mCurrentIds.containsKey(str)) {
+                mCurrentIds.put(str, mNextLong++);
+            }
+            long id = mCurrentIds.get(str);
+            int index = mUsedIds.getOrDefault(str, 0);
+            mUsedIds.put(str, index + 1);
+            return id + index * 10000;
+        }
+
+        private String genString(SliceItem item) {
+            final StringBuilder builder = new StringBuilder();
+            SliceQuery.stream(item).forEach(new Consumer<SliceItem>() {
+                @Override
+                public void accept(SliceItem i) {
+                    builder.append(i.getFormat());
+                    //i.removeHint(Slice.HINT_SELECTED);
+                    builder.append(i.getHints());
+                    switch (i.getFormat()) {
+                        case FORMAT_IMAGE:
+                            builder.append(i.getIcon());
+                            break;
+                        case FORMAT_TEXT:
+                            builder.append(i.getText());
+                            break;
+                        case FORMAT_INT:
+                            builder.append(i.getInt());
+                            break;
+                    }
+                }
+            });
+            return builder.toString();
+        }
+
+        public void resetUsage() {
+            mUsedIds.clear();
+        }
+    }
+}
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
new file mode 100644
index 0000000..2759394
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+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;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
+public class LargeTemplateView extends SliceChildView {
+
+    private final LargeSliceAdapter mAdapter;
+    private final RecyclerView mRecyclerView;
+    private final int mDefaultHeight;
+    private Slice mSlice;
+    private boolean mIsScrollable;
+
+    public LargeTemplateView(Context context) {
+        super(context);
+        mRecyclerView = new RecyclerView(getContext());
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        mAdapter = new LargeSliceAdapter(context);
+        mRecyclerView.setAdapter(mAdapter);
+        addView(mRecyclerView);
+        mDefaultHeight = getResources().getDimensionPixelSize(R.dimen.abc_slice_large_height);
+    }
+
+    @Override
+    public @SliceView.SliceMode int getMode() {
+        return SliceView.MODE_LARGE;
+    }
+
+    @Override
+    public void setSliceObserver(SliceView.SliceObserver observer) {
+        mObserver = observer;
+        if (mAdapter != null) {
+            mAdapter.setSliceObserver(mObserver);
+        }
+    }
+
+    @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();
+    }
+
+    @Override
+    public void setStyle(AttributeSet attrs) {
+        super.setStyle(attrs);
+        mAdapter.setStyle(attrs);
+    }
+
+    private void populate() {
+        if (mSlice == null) {
+            return;
+        }
+        ListContent lc = new ListContent(mSlice);
+        mAdapter.setSliceItems(lc.getRowItems(), mTintColor);
+    }
+
+    /**
+     * Whether or not the content in this template should be scrollable.
+     */
+    public void setScrollable(boolean isScrollable) {
+        // TODO -- restrict / enable how much this view can show
+        mIsScrollable = isScrollable;
+    }
+
+    @Override
+    public void resetView() {
+        mSlice = 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
new file mode 100644
index 0000000..86e9409
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/ListContent.java
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ */
+
+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.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 android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceHints;
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * Extracts information required to present content in a list format from a slice.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class ListContent {
+
+    private SliceItem mColorItem;
+    private SliceItem mSummaryItem;
+    private ArrayList<SliceItem> mRowItems = new ArrayList<>();
+    private boolean mHasHeader;
+
+    public ListContent(Slice slice) {
+        populate(slice);
+    }
+
+    /**
+     * Resets the content.
+     */
+    public void reset() {
+        mColorItem = null;
+        mSummaryItem = null;
+        mRowItems.clear();
+        mHasHeader = false;
+    }
+
+    /**
+     * @return whether this row has content that is valid to display.
+     */
+    public boolean populate(Slice slice) {
+        reset();
+        mColorItem = SliceQuery.findSubtype(slice, FORMAT_INT, SUBTYPE_COLOR);
+        // Find summary
+        SliceItem summaryItem = getSummaryItem(slice);
+        mSummaryItem = summaryItem;
+        // 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(SliceHints.HINT_SUMMARY, HINT_ACTIONS)
+                    && (FORMAT_ACTION.equals(format) || FORMAT_SLICE.equals(format))) {
+                if (!mHasHeader && !child.hasHint(HINT_LIST_ITEM)) {
+                    mHasHeader = true;
+                    mRowItems.add(0, child);
+                } else {
+                    mRowItems.add(child);
+                }
+            }
+        }
+        return isValid();
+    }
+
+    /**
+     * @return whether this list has content that is valid to display.
+     */
+    public boolean isValid() {
+        return mSummaryItem != null
+                || mRowItems.size() > 0;
+    }
+
+    @Nullable
+    public SliceItem getColorItem() {
+        return mColorItem;
+    }
+
+    @Nullable
+    public SliceItem getSummaryItem() {
+        return mSummaryItem;
+    }
+
+    public ArrayList<SliceItem> getRowItems() {
+        return mRowItems;
+    }
+
+    /**
+     * @return whether this list has a header or not.
+     */
+    public boolean hasHeader() {
+        return mHasHeader;
+    }
+
+    /**
+     * @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, SliceHints.HINT_SUMMARY, null);
+        if (summary != null) {
+            return summary;
+        }
+        // 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 Finds the first slice that has non-slice children.
+     */
+    private static SliceItem findFirstSlice(SliceItem slice) {
+        if (!FORMAT_SLICE.equals(slice.getFormat())) {
+            return slice;
+        }
+        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;
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/MessageView.java b/slices/view/src/main/java/androidx/app/slice/widget/MessageView.java
new file mode 100644
index 0000000..c519b69
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/MessageView.java
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import static android.app.slice.Slice.SUBTYPE_SOURCE;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.RestrictTo;
+import android.text.SpannableStringBuilder;
+import android.util.TypedValue;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.function.Consumer;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
+public class MessageView extends SliceChildView {
+
+    private TextView mDetails;
+    private ImageView mIcon;
+
+    private int mRowIndex;
+    private SliceView.SliceObserver mSliceObserver;
+
+    public MessageView(Context context) {
+        super(context);
+    }
+
+    @Override
+    public int getMode() {
+        return SliceView.MODE_LARGE;
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        // Do nothing it's always a list item
+    }
+
+    @Override
+    public void resetView() {
+        // TODO
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDetails = findViewById(android.R.id.summary);
+        mIcon = findViewById(android.R.id.icon);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice, boolean isHeader, int index,
+            SliceView.SliceObserver observer) {
+        mSliceObserver = observer;
+        mRowIndex = index;
+        SliceItem source = SliceQuery.findSubtype(slice, FORMAT_IMAGE, SUBTYPE_SOURCE);
+        if (source != null) {
+            final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    24, getContext().getResources().getDisplayMetrics());
+            // TODO: try and turn this into a drawable
+            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            Drawable d = source.getIcon().loadDrawable(getContext());
+            d.setBounds(0, 0, iconSize, iconSize);
+            d.draw(iconCanvas);
+            mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
+        }
+        final SpannableStringBuilder builder = new SpannableStringBuilder();
+        SliceQuery.findAll(slice, FORMAT_TEXT).forEach(new Consumer<SliceItem>() {
+            @Override
+            public void accept(SliceItem text) {
+                if (builder.length() != 0) {
+                    builder.append('\n');
+                }
+                builder.append(text.getText());
+            }
+        });
+        mDetails.setText(builder.toString());
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/RemoteInputView.java b/slices/view/src/main/java/androidx/app/slice/widget/RemoteInputView.java
new file mode 100644
index 0000000..da35018
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/RemoteInputView.java
@@ -0,0 +1,425 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import android.animation.Animator;
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.RestrictTo;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.app.slice.view.R;
+
+/**
+ * Host for the remote input.
+ *
+ * @hide
+ */
+// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(23)
+public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
+
+    private static final String TAG = "RemoteInput";
+
+    /**
+     * A marker object that let's us easily find views of this class.
+     */
+    public static final Object VIEW_TAG = new Object();
+
+    private RemoteEditText mEditText;
+    private ImageButton mSendButton;
+    private ProgressBar mProgressBar;
+    private PendingIntent mPendingIntent;
+    private RemoteInput[] mRemoteInputs;
+    private RemoteInput mRemoteInput;
+
+    private int mRevealCx;
+    private int mRevealCy;
+    private int mRevealR;
+    private boolean mResetting;
+
+    public RemoteInputView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mProgressBar = findViewById(R.id.remote_input_progress);
+        mSendButton = findViewById(R.id.remote_input_send);
+        mSendButton.setOnClickListener(this);
+
+        mEditText = (RemoteEditText) getChildAt(0);
+        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                final boolean isSoftImeEvent = event == null
+                        && (actionId == EditorInfo.IME_ACTION_DONE
+                                || actionId == EditorInfo.IME_ACTION_NEXT
+                                || actionId == EditorInfo.IME_ACTION_SEND);
+                final boolean isKeyboardEnterKey = event != null
+                        && isConfirmKey(event.getKeyCode())
+                        && event.getAction() == KeyEvent.ACTION_DOWN;
+
+                if (isSoftImeEvent || isKeyboardEnterKey) {
+                    if (mEditText.length() > 0) {
+                        sendRemoteInput();
+                    }
+                    // Consume action to prevent IME from closing.
+                    return true;
+                }
+                return false;
+            }
+        });
+        mEditText.addTextChangedListener(this);
+        mEditText.setInnerFocusable(false);
+        mEditText.mRemoteInputView = this;
+    }
+
+    private void sendRemoteInput() {
+        Bundle results = new Bundle();
+        results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
+                results);
+
+        mEditText.setEnabled(false);
+        mSendButton.setVisibility(INVISIBLE);
+        mProgressBar.setVisibility(VISIBLE);
+        mEditText.mShowImeOnInputConnection = false;
+
+        // TODO: Figure out API for telling the system about slice interaction.
+        // Tell ShortcutManager that this package has been "activated".  ShortcutManager
+        // will reset the throttling for this package.
+        // Strictly speaking, the intent receiver may be different from the intent creator,
+        // but that's an edge case, and also because we can't always know which package will receive
+        // an intent, so we just reset for the creator.
+        //getContext().getSystemService(ShortcutManager.class).onApplicationActive(
+        //        mPendingIntent.getCreatorPackage(),
+        //        getContext().getUserId());
+
+        try {
+            mPendingIntent.send(getContext(), 0, fillInIntent);
+            reset();
+        } catch (PendingIntent.CanceledException e) {
+            Log.i(TAG, "Unable to send remote input result", e);
+            Toast.makeText(getContext(), "Failure sending pending intent for inline reply :(",
+                    Toast.LENGTH_SHORT).show();
+            reset();
+        }
+    }
+
+    /**
+     * Creates a remote input view.
+     */
+    public static RemoteInputView inflate(Context context, ViewGroup root) {
+        RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
+                R.layout.abc_slice_remote_input, root, false);
+        v.setTag(VIEW_TAG);
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mSendButton) {
+            sendRemoteInput();
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        // We never want for a touch to escape to an outer view or one we covered.
+        return true;
+    }
+
+    private void onDefocus() {
+        setVisibility(INVISIBLE);
+    }
+
+    /**
+     * Set the pending intent for remote input.
+     */
+    public void setPendingIntent(PendingIntent pendingIntent) {
+        mPendingIntent = pendingIntent;
+    }
+
+    /**
+     * Set the remote inputs for this view.
+     */
+    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+        mRemoteInputs = remoteInputs;
+        mRemoteInput = remoteInput;
+        mEditText.setHint(mRemoteInput.getLabel());
+    }
+
+    /**
+     * Focuses the remote input view.
+     */
+    public void focusAnimated() {
+        if (getVisibility() != VISIBLE) {
+            Animator animator = ViewAnimationUtils.createCircularReveal(
+                    this, mRevealCx, mRevealCy, 0, mRevealR);
+            animator.setDuration(200);
+            animator.start();
+        }
+        focus();
+    }
+
+    private void focus() {
+        setVisibility(VISIBLE);
+        mEditText.setInnerFocusable(true);
+        mEditText.mShowImeOnInputConnection = true;
+        mEditText.setSelection(mEditText.getText().length());
+        mEditText.requestFocus();
+        updateSendButton();
+    }
+
+    private void reset() {
+        mResetting = true;
+
+        mEditText.getText().clear();
+        mEditText.setEnabled(true);
+        mSendButton.setVisibility(VISIBLE);
+        mProgressBar.setVisibility(INVISIBLE);
+        updateSendButton();
+        onDefocus();
+
+        mResetting = false;
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (mResetting && child == mEditText) {
+            // Suppress text events if it happens during resetting. Ideally this would be
+            // suppressed by the text view not being shown, but that doesn't work here because it
+            // needs to stay visible for the animation.
+            return false;
+        }
+        return super.onRequestSendAccessibilityEvent(child, event);
+    }
+
+    private void updateSendButton() {
+        mSendButton.setEnabled(mEditText.getText().length() != 0);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        updateSendButton();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setRevealParameters(int cx, int cy, int r) {
+        mRevealCx = cx;
+        mRevealCy = cy;
+        mRevealR = r;
+    }
+
+    @Override
+    public void dispatchStartTemporaryDetach() {
+        super.dispatchStartTemporaryDetach();
+        // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
+        // won't lose IME focus.
+        detachViewFromParent(mEditText);
+    }
+
+    @Override
+    public void dispatchFinishTemporaryDetach() {
+        if (isAttachedToWindow()) {
+            attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
+        } else {
+            removeDetachedView(mEditText, false /* animate */);
+        }
+        super.dispatchFinishTemporaryDetach();
+    }
+
+    /**
+     * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
+     * whenever the user navigates away from it or it becomes invisible.
+     */
+    public static class RemoteEditText extends EditText {
+
+        private final Drawable mBackground;
+        private RemoteInputView mRemoteInputView;
+        boolean mShowImeOnInputConnection;
+
+        public RemoteEditText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mBackground = getBackground();
+        }
+
+        private void defocusIfNeeded(boolean animate) {
+            if (mRemoteInputView != null || isTemporarilyDetached()) {
+                if (isTemporarilyDetached()) {
+                    // We might get reattached but then the other one of HUN / expanded might steal
+                    // our focus, so we'll need to save our text here.
+                }
+                return;
+            }
+            if (isFocusable() && isEnabled()) {
+                setInnerFocusable(false);
+                if (mRemoteInputView != null) {
+                    mRemoteInputView.onDefocus();
+                }
+                mShowImeOnInputConnection = false;
+            }
+        }
+
+        @Override
+        protected void onVisibilityChanged(View changedView, int visibility) {
+            super.onVisibilityChanged(changedView, visibility);
+
+            if (!isShown()) {
+                defocusIfNeeded(false /* animate */);
+            }
+        }
+
+        @Override
+        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+            super.onFocusChanged(focused, direction, previouslyFocusedRect);
+            if (!focused) {
+                defocusIfNeeded(true /* animate */);
+            }
+        }
+
+        @Override
+        public void getFocusedRect(Rect r) {
+            super.getFocusedRect(r);
+            r.top = getScrollY();
+            r.bottom = getScrollY() + (getBottom() - getTop());
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                // Eat the DOWN event here to prevent any default behavior.
+                return true;
+            }
+            return super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                defocusIfNeeded(true /* animate */);
+                return true;
+            }
+            return super.onKeyUp(keyCode, event);
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
+
+            if (mShowImeOnInputConnection && inputConnection != null) {
+                final InputMethodManager imm = getContext().getSystemService(
+                        InputMethodManager.class);
+                if (imm != null) {
+                    // onCreateInputConnection is called by InputMethodManager in the middle of
+                    // setting up the connection to the IME; wait with requesting the IME until that
+                    // work has completed.
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            imm.viewClicked(RemoteEditText.this);
+                            imm.showSoftInput(RemoteEditText.this, 0);
+                        }
+                    });
+                }
+            }
+
+            return inputConnection;
+        }
+
+        @Override
+        public void onCommitCompletion(CompletionInfo text) {
+            clearComposingText();
+            setText(text.getText());
+            setSelection(getText().length());
+        }
+
+        void setInnerFocusable(boolean focusable) {
+            setFocusableInTouchMode(focusable);
+            setFocusable(focusable);
+            setCursorVisible(focusable);
+
+            if (focusable) {
+                requestFocus();
+                setBackground(mBackground);
+            } else {
+                setBackground(null);
+            }
+
+        }
+    }
+
+    /** Whether key will, by default, trigger a click on the focused view.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public static final boolean isConfirmKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_SPACE:
+            case KeyEvent.KEYCODE_NUMPAD_ENTER:
+                return true;
+            default:
+                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
new file mode 100644
index 0000000..0e730a5
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/RowContent.java
@@ -0,0 +1,246 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.Slice.SUBTYPE_SLIDER;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_INT;
+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 android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceHints;
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * Extracts information required to present content in a row format from a slice.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class RowContent {
+    private static final String TAG = "RowContent";
+
+    private SliceItem mContentIntent;
+    private SliceItem mStartItem;
+    private SliceItem mTitleItem;
+    private SliceItem mSubtitleItem;
+    private ArrayList<SliceItem> mEndItems = new ArrayList<>();
+    private boolean mEndItemsContainAction;
+    private SliceItem mSlider;
+
+    public RowContent(SliceItem rowSlice, boolean showStartItem) {
+        populate(rowSlice, showStartItem);
+    }
+
+    /**
+     * Resets the content.
+     */
+    public void reset() {
+        mContentIntent = null;
+        mStartItem = null;
+        mTitleItem = null;
+        mSubtitleItem = null;
+        mEndItems.clear();
+    }
+
+    /**
+     * @return whether this row has content that is valid to display.
+     */
+    public boolean populate(SliceItem rowSlice, boolean showStartItem) {
+        reset();
+        if (!isValidRow(rowSlice)) {
+            Log.w(TAG, "Provided SliceItem is invalid for RowContent");
+            return false;
+        }
+        // Filter anything not viable for displaying in a row
+        ArrayList<SliceItem> rowItems = filterInvalidItems(rowSlice);
+        // If we've only got one item that's a slice / action use those items instead
+        if (rowItems.size() == 1 && (FORMAT_ACTION.equals(rowItems.get(0).getFormat())
+                || FORMAT_SLICE.equals(rowItems.get(0).getFormat()))) {
+            if (isValidRow(rowItems.get(0))) {
+                rowSlice = rowItems.get(0);
+                rowItems = filterInvalidItems(rowSlice);
+            }
+        }
+        // Content intent
+        if (FORMAT_ACTION.equals(rowSlice.getFormat())) {
+            mContentIntent = rowSlice;
+        }
+        if (SUBTYPE_SLIDER.equals(rowSlice.getSubType())) {
+            mSlider = rowSlice;
+        }
+        if (rowItems.size() > 0) {
+            // Start item
+            if (isStartType(rowItems.get(0))) {
+                if (showStartItem) {
+                    mStartItem = rowItems.get(0);
+                }
+                rowItems.remove(0);
+            }
+            // Text + end items
+            ArrayList<SliceItem> endItems = new ArrayList<>();
+            for (int i = 0; i < rowItems.size(); i++) {
+                final SliceItem item = rowItems.get(i);
+                if (FORMAT_TEXT.equals(item.getFormat())) {
+                    if ((mTitleItem == null || !mTitleItem.hasHint(HINT_TITLE))
+                            && item.hasHint(HINT_TITLE)) {
+                        mTitleItem = item;
+                    } else if (mSubtitleItem == null) {
+                        mSubtitleItem = item;
+                    }
+                } else {
+                    endItems.add(item);
+                }
+            }
+            // Special rules for end items: only one timestamp, can't be mixture of icons / actions
+            boolean hasTimestamp = mStartItem != null
+                    && FORMAT_TIMESTAMP.equals(mStartItem.getFormat());
+            String desiredFormat = null;
+            for (int i = 0; i < endItems.size(); i++) {
+                final SliceItem item = endItems.get(i);
+                if (FORMAT_TIMESTAMP.equals(item.getFormat())) {
+                    if (!hasTimestamp) {
+                        hasTimestamp = true;
+                        mEndItems.add(item);
+                    }
+                } else if (desiredFormat == null) {
+                    desiredFormat = item.getFormat();
+                    mEndItems.add(item);
+                } else if (desiredFormat.equals(item.getFormat())) {
+                    mEndItems.add(item);
+                    mEndItemsContainAction |= FORMAT_ACTION.equals(item.getFormat());
+                }
+            }
+        }
+        return isValid();
+    }
+
+    /**
+     * @return the {@link SliceItem} representing the slider in this row; can be null
+     */
+    @Nullable
+    public SliceItem getSlider() {
+        return mSlider;
+    }
+
+    /**
+     * @return whether this row has content that is valid to display.
+     */
+    public boolean isValid() {
+        return mStartItem != null
+                || mTitleItem != null
+                || mSubtitleItem != null
+                || mEndItems.size() > 0;
+    }
+
+    @Nullable
+    public SliceItem getContentIntent() {
+        return mContentIntent;
+    }
+
+    @Nullable
+    public SliceItem getStartItem() {
+        return mStartItem;
+    }
+
+    @Nullable
+    public SliceItem getTitleItem() {
+        return mTitleItem;
+    }
+
+    @Nullable
+    public SliceItem getSubtitleItem() {
+        return mSubtitleItem;
+    }
+
+    public ArrayList<SliceItem> getEndItems() {
+        return mEndItems;
+    }
+
+    /**
+     * @return whether {@link #getEndItems()} contains a SliceItem with FORMAT_ACTION
+     */
+    public boolean endItemsContainAction() {
+        return mEndItemsContainAction;
+    }
+
+    /**
+     * @return whether this is a valid item to use to populate a row of content.
+     */
+    private static boolean isValidRow(SliceItem rowSlice) {
+        // Must be slice or action
+        if (FORMAT_SLICE.equals(rowSlice.getFormat())
+                || FORMAT_ACTION.equals(rowSlice.getFormat())) {
+            // Must have at least one legitimate child
+            List<SliceItem> rowItems = rowSlice.getSlice().getItems();
+            for (int i = 0; i < rowItems.size(); i++) {
+                if (isValidRowContent(rowSlice, rowItems.get(i))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static ArrayList<SliceItem> filterInvalidItems(SliceItem rowSlice) {
+        ArrayList<SliceItem> filteredList = new ArrayList<>();
+        for (SliceItem i : rowSlice.getSlice().getItems()) {
+            if (isValidRowContent(rowSlice, i)) {
+                filteredList.add(i);
+            }
+        }
+        return filteredList;
+    }
+
+    /**
+     * @return whether this item has valid content to display in a row.
+     */
+    private static boolean isValidRowContent(SliceItem slice, SliceItem item) {
+        // TODO -- filter for shortcut once that's in
+        final String itemFormat = item.getFormat();
+        // Must be a format that is presentable
+        return FORMAT_TEXT.equals(itemFormat)
+                || FORMAT_IMAGE.equals(itemFormat)
+                || FORMAT_TIMESTAMP.equals(itemFormat)
+                || FORMAT_REMOTE_INPUT.equals(itemFormat)
+                || FORMAT_ACTION.equals(itemFormat)
+                || (FORMAT_INT.equals(itemFormat) && SUBTYPE_SLIDER.equals(slice.getSubType()));
+    }
+
+    /**
+     * @return Whether this item is appropriate to be considered a "start" item, i.e. go in the
+     *         front slot of a row.
+     */
+    private static boolean isStartType(SliceItem item) {
+        final String type = item.getFormat();
+        return (!item.hasHint(SliceHints.SUBTYPE_TOGGLE)
+                && (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
new file mode 100644
index 0000000..417c069
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/RowView.java
@@ -0,0 +1,438 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_SELECTED;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_INT;
+import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import static androidx.app.slice.core.SliceHints.EXTRA_SLIDER_VALUE;
+import static androidx.app.slice.core.SliceHints.EXTRA_TOGGLE_STATE;
+import static androidx.app.slice.core.SliceHints.SUBTYPE_MAX;
+import static androidx.app.slice.core.SliceHints.SUBTYPE_PROGRESS;
+import static androidx.app.slice.core.SliceHints.SUBTYPE_TOGGLE;
+import static androidx.app.slice.widget.SliceView.MODE_LARGE;
+import static androidx.app.slice.widget.SliceView.MODE_SMALL;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.support.annotation.ColorInt;
+import android.support.annotation.RestrictTo;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * Row item is in small template format and can be used to construct list items for use
+ * with {@link LargeTemplateView}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(23)
+public class RowView extends SliceChildView implements View.OnClickListener {
+
+    private static final String TAG = "RowView";
+
+    // The number of items that fit on the right hand side of a small slice
+    private static final int MAX_END_ITEMS = 3;
+
+    private LinearLayout mStartContainer;
+    private LinearLayout mContent;
+    private TextView mPrimaryText;
+    private TextView mSecondaryText;
+    private View mDivider;
+    private ArrayList<CompoundButton> mToggles = new ArrayList<>();
+    private LinearLayout mEndContainer;
+    private SeekBar mSeekBar;
+    private ProgressBar mProgressBar;
+
+    private boolean mInSmallMode;
+    private int mRowIndex;
+    private RowContent mRowContent;
+    private SliceItem mRowAction;
+    private boolean mIsHeader;
+
+    private int mIconSize;
+    private int mPadding;
+
+    public RowView(Context context) {
+        super(context);
+        mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_icon_size);
+        mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_padding);
+        inflate(context, R.layout.abc_slice_small_template, this);
+
+        mStartContainer = (LinearLayout) findViewById(R.id.icon_frame);
+        mContent = (LinearLayout) findViewById(android.R.id.content);
+        mPrimaryText = (TextView) findViewById(android.R.id.title);
+        mSecondaryText = (TextView) findViewById(android.R.id.summary);
+        mDivider = findViewById(R.id.divider);
+        mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
+        mSeekBar = (SeekBar) findViewById(R.id.seek_bar);
+        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
+    }
+
+    @Override
+    public @SliceView.SliceMode int getMode() {
+        return mInSmallMode ? MODE_SMALL : MODE_LARGE;
+    }
+
+    @Override
+    public void setTint(@ColorInt int tintColor) {
+        super.setTint(tintColor);
+        if (mRowContent != null) {
+            // TODO -- can be smarter about this
+            resetView();
+            populateViews();
+        }
+    }
+
+    /**
+     * This is called when RowView is being used as a component in a large template.
+     */
+    @Override
+    public void setSliceItem(SliceItem slice, boolean isHeader, int index,
+            SliceView.SliceObserver observer) {
+        mInSmallMode = false;
+        setSliceObserver(observer);
+        mRowIndex = index;
+        mIsHeader = isHeader;
+        mRowContent = new RowContent(slice, !mIsHeader /* showStartItem */);
+        populateViews();
+    }
+
+    /**
+     * This is called when RowView is being used as a small template.
+     */
+    @Override
+    public void setSlice(Slice slice) {
+        mInSmallMode = true;
+        mRowIndex = 0;
+        mIsHeader = true;
+        ListContent lc = new ListContent(slice);
+        mRowContent = new RowContent(lc.getSummaryItem(), false /* showStartItem */);
+        populateViews();
+    }
+
+    private void populateViews() {
+        resetView();
+        boolean showStart = false;
+        final SliceItem startItem = mRowContent.getStartItem();
+        if (startItem != null) {
+            final EventInfo info = new EventInfo(getMode(),
+                    EventInfo.ACTION_TYPE_BUTTON,
+                    EventInfo.ROW_TYPE_LIST, mRowIndex);
+            info.setPosition(EventInfo.POSITION_START, 0, 1);
+            showStart = addItem(startItem, mTintColor, true /* isStart */, 0 /* padding */, info);
+        }
+        mStartContainer.setVisibility(showStart ? View.VISIBLE : View.GONE);
+
+        final SliceItem titleItem = mRowContent.getTitleItem();
+        if (titleItem != null) {
+            mPrimaryText.setText(titleItem.getText());
+        }
+        mPrimaryText.setVisibility(titleItem != null ? View.VISIBLE : View.GONE);
+
+        final SliceItem subTitle = mRowContent.getSubtitleItem();
+        if (subTitle != null) {
+            mSecondaryText.setText(subTitle.getText());
+        }
+        mSecondaryText.setVisibility(subTitle != null ? View.VISIBLE : View.GONE);
+
+        final SliceItem slider = mRowContent.getSlider();
+        if (slider != null) {
+            addSlider(slider);
+            return;
+        }
+
+        mRowAction = mRowContent.getContentIntent();
+        ArrayList<SliceItem> endItems = mRowContent.getEndItems();
+        if (endItems.isEmpty()) {
+            return;
+        }
+
+        // If we're here we might be able to show end items
+        int itemCount = 0;
+        // Prefer to show actions as end items if possible; fall back to the first format type.
+        String desiredFormat = mRowContent.endItemsContainAction()
+                ? FORMAT_ACTION : endItems.get(0).getFormat();
+        boolean firstItemIsADefaultToggle = false;
+        for (int i = 0; i < endItems.size(); i++) {
+            final SliceItem endItem = endItems.get(i);
+            final String endFormat = endItem.getFormat();
+            // Only show one type of format at the end of the slice, use whatever is first
+            if (itemCount <= MAX_END_ITEMS
+                    && (desiredFormat.equals(endFormat)
+                    || FORMAT_TIMESTAMP.equals(endFormat))) {
+                final EventInfo info = new EventInfo(getMode(),
+                        EventInfo.ACTION_TYPE_BUTTON,
+                        EventInfo.ROW_TYPE_LIST, mRowIndex);
+                info.setPosition(EventInfo.POSITION_END, i,
+                        Math.min(endItems.size(), MAX_END_ITEMS));
+                if (addItem(endItem, mTintColor, false /* isStart */, mPadding, info)) {
+                    itemCount++;
+                    if (itemCount == 1) {
+                        firstItemIsADefaultToggle = !mToggles.isEmpty()
+                                && SliceQuery.find(endItem.getSlice(), FORMAT_IMAGE) == null;
+                    }
+                }
+            }
+        }
+
+        boolean hasRowAction = mRowAction != null;
+        boolean hasEndItemAction = FORMAT_ACTION.contentEquals(desiredFormat);
+        // If there is a row action and the first end item is a default toggle, show the divider.
+        mDivider.setVisibility(hasRowAction && firstItemIsADefaultToggle
+                ? View.VISIBLE : View.GONE);
+        if (hasRowAction) {
+            if (itemCount > 0 && hasEndItemAction) {
+                setViewClickable(mContent, true);
+            } else {
+                setViewClickable(this, true);
+            }
+        } else {
+            // If the only end item is an action, make the whole row clickable.
+            if (mRowContent.endItemsContainAction() && itemCount == 1) {
+                setViewClickable(this, true);
+            }
+        }
+    }
+
+    private void addSlider(final SliceItem slider) {
+        final ProgressBar progressBar;
+        if (FORMAT_ACTION.equals(slider.getFormat())) {
+            // Seek bar
+            progressBar = mSeekBar;
+            mSeekBar.setVisibility(View.VISIBLE);
+            SliceItem thumb = SliceQuery.find(slider, FORMAT_IMAGE);
+            if (thumb != null) {
+                mSeekBar.setThumb(thumb.getIcon().loadDrawable(getContext()));
+            }
+            mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+                @Override
+                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                    try {
+                        PendingIntent pi = slider.getAction();
+                        Intent i = new Intent().putExtra(EXTRA_SLIDER_VALUE, progress);
+                        // TODO: sending this PendingIntent should be rate limited.
+                        pi.send(getContext(), 0, i, null, null);
+                    } catch (CanceledException e) { }
+                }
+
+                @Override
+                public void onStartTrackingTouch(SeekBar seekBar) { }
+
+                @Override
+                public void onStopTrackingTouch(SeekBar seekBar) { }
+            });
+        } else {
+            // Progress bar
+            progressBar = mProgressBar;
+            mProgressBar.setVisibility(View.VISIBLE);
+        }
+        SliceItem max = SliceQuery.findSubtype(slider, FORMAT_INT, SUBTYPE_MAX);
+        if (max != null) {
+            progressBar.setMax(max.getInt());
+        }
+        SliceItem progress = SliceQuery.findSubtype(slider, FORMAT_INT, SUBTYPE_PROGRESS);
+        if (progress != null) {
+            progressBar.setProgress(progress.getInt());
+        }
+    }
+
+    /**
+     * Add a toggle view to container.
+     */
+    private void addToggle(final SliceItem toggleItem, int color, ViewGroup container) {
+        // Check if this is a custom toggle
+        Icon checkedIcon = null;
+        List<SliceItem> sliceItems = toggleItem.getSlice().getItems();
+        if (sliceItems.size() > 0) {
+            checkedIcon = FORMAT_IMAGE.equals(sliceItems.get(0).getFormat())
+                    ? sliceItems.get(0).getIcon()
+                    : null;
+        }
+        final CompoundButton toggle;
+        if (checkedIcon != null) {
+            if (color != -1) {
+                // TODO - Should custom toggle buttons be tinted? What if the app wants diff
+                // colors per state?
+                checkedIcon.setTint(color);
+            }
+            toggle = new ToggleButton(getContext());
+            ((ToggleButton) toggle).setTextOff("");
+            ((ToggleButton) toggle).setTextOn("");
+            toggle.setBackground(checkedIcon.loadDrawable(getContext()));
+            container.addView(toggle);
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) toggle.getLayoutParams();
+            lp.width = mIconSize;
+            lp.height = mIconSize;
+        } else {
+            toggle = new Switch(getContext());
+            container.addView(toggle);
+        }
+        toggle.setChecked(SliceQuery.hasHints(toggleItem.getSlice(), HINT_SELECTED));
+        toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                try {
+                    PendingIntent pi = toggleItem.getAction();
+                    Intent i = new Intent().putExtra(EXTRA_TOGGLE_STATE, isChecked);
+                    pi.send(getContext(), 0, i, null, null);
+                    if (mObserver != null) {
+                        final EventInfo info = new EventInfo(getMode(),
+                                EventInfo.ACTION_TYPE_TOGGLE,
+                                EventInfo.ROW_TYPE_LIST, mRowIndex);
+                        info.state = isChecked ? EventInfo.STATE_ON : EventInfo.STATE_OFF;
+                        mObserver.onSliceAction(info, toggleItem);
+                    }
+                } catch (CanceledException e) {
+                    toggle.setSelected(!isChecked);
+                }
+            }
+        });
+        mToggles.add(toggle);
+    }
+
+    /**
+     * Adds simple items to a container. Simple items include actions with icons, images, or
+     * timestamps.
+     */
+    private boolean addItem(SliceItem sliceItem, int color, boolean isStart, int padding,
+            final EventInfo info) {
+        SliceItem image = null;
+        SliceItem action = null;
+        SliceItem timeStamp = null;
+        ViewGroup container = isStart ? mStartContainer : mEndContainer;
+        if (FORMAT_ACTION.equals(sliceItem.getFormat())) {
+            if (SliceQuery.hasHints(sliceItem.getSlice(), SUBTYPE_TOGGLE)) {
+                addToggle(sliceItem, color, container);
+                return true;
+            }
+            image = SliceQuery.find(sliceItem.getSlice(), FORMAT_IMAGE);
+            timeStamp = SliceQuery.find(sliceItem.getSlice(), FORMAT_TIMESTAMP);
+            action = sliceItem;
+        } else if (FORMAT_IMAGE.equals(sliceItem.getFormat())) {
+            image = sliceItem;
+        } else if (FORMAT_TIMESTAMP.equals(sliceItem.getFormat())) {
+            timeStamp = sliceItem;
+        }
+        View addedView = null;
+        if (image != null) {
+            ImageView iv = new ImageView(getContext());
+            iv.setImageIcon(image.getIcon());
+            if (color != -1 && !sliceItem.hasHint(HINT_NO_TINT)) {
+                iv.setColorFilter(color);
+            }
+            container.addView(iv);
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
+            lp.width = mIconSize;
+            lp.height = mIconSize;
+            lp.setMarginStart(padding);
+            addedView = iv;
+        } else if (timeStamp != null) {
+            TextView tv = new TextView(getContext());
+            tv.setText(SliceViewUtil.getRelativeTimeString(sliceItem.getTimestamp()));
+            container.addView(tv);
+            addedView = tv;
+        }
+        if (action != null && addedView != null) {
+            final SliceItem sliceAction = action;
+            addedView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    try {
+                        sliceAction.getAction().send();
+                        if (mObserver != null) {
+                            mObserver.onSliceAction(info, sliceAction);
+                        }
+                    } catch (CanceledException e) {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            addedView.setBackground(SliceViewUtil.getDrawable(getContext(),
+                    android.R.attr.selectableItemBackground));
+        }
+        return addedView != null;
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (mRowAction != null && FORMAT_ACTION.equals(mRowAction.getFormat())) {
+            // Check for a row action
+            try {
+                mRowAction.getAction().send();
+                if (mObserver != null) {
+                    EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_CONTENT,
+                            EventInfo.ROW_TYPE_LIST, mRowIndex);
+                    mObserver.onSliceAction(info, mRowAction);
+                }
+            } catch (CanceledException e) {
+                Log.w(TAG, "PendingIntent for slice cannot be sent", e);
+            }
+        } else if (mToggles.size() == 1) {
+            // If there is only one toggle and no row action, just toggle it.
+            mToggles.get(0).toggle();
+        }
+    }
+
+    private void setViewClickable(View layout, boolean isClickable) {
+        layout.setOnClickListener(isClickable ? this : null);
+        layout.setBackground(isClickable ? SliceViewUtil.getDrawable(getContext(),
+                android.R.attr.selectableItemBackground) : null);
+        layout.setClickable(isClickable);
+    }
+
+    @Override
+    public void resetView() {
+        setViewClickable(this, false);
+        setViewClickable(mContent, false);
+        mStartContainer.removeAllViews();
+        mEndContainer.removeAllViews();
+        mPrimaryText.setText(null);
+        mSecondaryText.setText(null);
+        mToggles.clear();
+        mRowAction = null;
+        mDivider.setVisibility(View.GONE);
+        mSeekBar.setVisibility(View.GONE);
+        mProgressBar.setVisibility(View.GONE);
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/ShortcutView.java b/slices/view/src/main/java/androidx/app/slice/widget/ShortcutView.java
new file mode 100644
index 0000000..180aaff
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/ShortcutView.java
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.Slice.SUBTYPE_COLOR;
+import static android.app.slice.Slice.SUBTYPE_SOURCE;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+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.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.net.Uri;
+import android.support.annotation.RestrictTo;
+import android.widget.ImageView;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(23)
+public class ShortcutView extends SliceChildView {
+
+    private static final String TAG = "ShortcutView";
+
+    private Slice mSlice;
+    private Uri mUri;
+    private SliceItem mActionItem;
+    private SliceItem mLabel;
+    private SliceItem mIcon;
+
+    private int mLargeIconSize;
+    private int mSmallIconSize;
+
+    private SliceView.SliceObserver mObserver;
+
+    public ShortcutView(Context context) {
+        super(context);
+        final Resources res = getResources();
+        mSmallIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_icon_size);
+        mLargeIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_shortcut_size);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        resetView();
+        mSlice = slice;
+        determineShortcutItems(getContext(), slice);
+        SliceItem colorItem = SliceQuery.findSubtype(slice, FORMAT_INT, SUBTYPE_COLOR);
+        if (colorItem == null) {
+            colorItem = SliceQuery.findSubtype(slice, FORMAT_INT, SUBTYPE_COLOR);
+        }
+        final int color = colorItem != null
+                ? colorItem.getInt()
+                : SliceViewUtil.getColorAccent(getContext());
+        ShapeDrawable circle = new ShapeDrawable(new OvalShape());
+        circle.setTint(color);
+        ImageView iv = new ImageView(getContext());
+        iv.setBackground(circle);
+        addView(iv);
+        if (mIcon != null) {
+            final boolean isLarge = mIcon.hasHint(HINT_LARGE)
+                    || SUBTYPE_SOURCE.equals(mIcon.getSubType());
+            final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
+            SliceViewUtil.createCircledIcon(getContext(), iconSize, mIcon.getIcon(),
+                    isLarge, this /* parent */);
+            mUri = slice.getUri();
+            setClickable(true);
+        } else {
+            setClickable(false);
+        }
+    }
+
+    @Override
+    public @SliceView.SliceMode int getMode() {
+        return SliceView.MODE_SHORTCUT;
+    }
+
+    @Override
+    public boolean performClick() {
+        if (!callOnClick()) {
+            try {
+                if (mActionItem != null) {
+                    mActionItem.getAction().send();
+                } else {
+                    Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    getContext().startActivity(intent);
+                }
+                if (mObserver != null) {
+                    EventInfo ei = new EventInfo(SliceView.MODE_SHORTCUT,
+                            EventInfo.ACTION_TYPE_BUTTON,
+                            EventInfo.ROW_TYPE_SHORTCUT, 0 /* rowIndex */);
+                    SliceItem interactedItem = mActionItem != null
+                            ? mActionItem
+                            : new SliceItem(mSlice, FORMAT_SLICE, null /* subtype */,
+                                    mSlice.getHints());
+                    mObserver.onSliceAction(ei, interactedItem);
+                }
+            } catch (CanceledException e) {
+                e.printStackTrace();
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Looks at the slice and determines which items are best to use to compose the shortcut.
+     */
+    private void determineShortcutItems(Context context, Slice slice) {
+        SliceItem titleItem = SliceQuery.find(slice, FORMAT_ACTION,
+                HINT_TITLE, null);
+
+        if (titleItem != null) {
+            // Preferred case: hinted action containing hinted image and text
+            mActionItem = titleItem;
+            mIcon = SliceQuery.find(titleItem.getSlice(), FORMAT_IMAGE, HINT_TITLE,
+                    null);
+            mLabel = SliceQuery.find(titleItem.getSlice(), FORMAT_TEXT, HINT_TITLE,
+                    null);
+        } else {
+            // No hinted action; just use the first one
+            mActionItem = SliceQuery.find(slice, FORMAT_ACTION, (String) null, null);
+        }
+        // First fallback: any hinted image and text
+        if (mIcon == null) {
+            mIcon = SliceQuery.find(slice, FORMAT_IMAGE, HINT_TITLE,
+                    null);
+        }
+        if (mLabel == null) {
+            mLabel = SliceQuery.find(slice, FORMAT_TEXT, HINT_TITLE,
+                    null);
+        }
+        // Second fallback: first image and text
+        if (mIcon == null) {
+            mIcon = SliceQuery.find(slice, FORMAT_IMAGE, (String) null,
+                    null);
+        }
+        if (mLabel == null) {
+            mLabel = SliceQuery.find(slice, FORMAT_TEXT, (String) null,
+                    null);
+        }
+        // Final fallback: use app info
+        if (mIcon == null || mLabel == null || mActionItem == null) {
+            PackageManager pm = context.getPackageManager();
+            ProviderInfo providerInfo = pm.resolveContentProvider(
+                    slice.getUri().getAuthority(), 0);
+            ApplicationInfo appInfo = providerInfo.applicationInfo;
+            if (appInfo != null) {
+                if (mIcon == null) {
+                    Slice.Builder sb = new Slice.Builder(slice.getUri());
+                    Drawable icon = pm.getApplicationIcon(appInfo);
+                    sb.addIcon(SliceViewUtil.createIconFromDrawable(icon), HINT_LARGE);
+                    mIcon = sb.build().getItems().get(0);
+                }
+                if (mLabel == null) {
+                    Slice.Builder sb = new Slice.Builder(slice.getUri());
+                    sb.addText(pm.getApplicationLabel(appInfo), null);
+                    mLabel = sb.build().getItems().get(0);
+                }
+                if (mActionItem == null) {
+                    mActionItem = new SliceItem(PendingIntent.getActivity(context, 0,
+                            pm.getLaunchIntentForPackage(appInfo.packageName), 0),
+                            new Slice.Builder(slice.getUri()).build(), FORMAT_SLICE,
+                            null /* subtype */, null);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void resetView() {
+        mSlice = null;
+        mUri = null;
+        mActionItem = null;
+        mLabel = null;
+        mIcon = null;
+        setBackground(null);
+        removeAllViews();
+    }
+}
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
new file mode 100644
index 0000000..75e5094
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceChildView.java
@@ -0,0 +1,98 @@
+/*
+ * 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.app.slice.widget;
+
+import android.content.Context;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+
+/**
+ * Base class for children views of {@link SliceView}.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public abstract class SliceChildView extends FrameLayout {
+
+    protected SliceView.SliceObserver mObserver;
+    protected int mTintColor;
+
+    public SliceChildView(@NonNull Context context) {
+        super(context);
+    }
+
+    public SliceChildView(Context context, AttributeSet attributeSet) {
+        this(context);
+    }
+
+    /**
+     * @return the mode of the slice being presented.
+     */
+    public abstract int getMode();
+
+    /**
+     * @param slice the slice to show in this view.
+     */
+    public abstract void setSlice(Slice slice);
+
+    /**
+     * Called when the view should be reset.
+     */
+    public abstract void resetView();
+
+    /**
+     * @return the view.
+     */
+    public View getView() {
+        return this;
+    }
+
+    /**
+     * Sets a custom color to use for tinting elements like icons for this view.
+     */
+    public void setTint(@ColorInt int tintColor) {
+        mTintColor = tintColor;
+    }
+
+    /**
+     * Sets the observer to notify when an interaction events occur on the view.
+     */
+    public void setSliceObserver(SliceView.SliceObserver observer) {
+        mObserver = observer;
+    }
+
+    /**
+     * Populates style information for this view.
+     */
+    public void setStyle(AttributeSet attrs) {
+        // TODO
+    }
+
+    /**
+     * Called when the slice being displayed in this view is an element of a larger list.
+     */
+    public void setSliceItem(SliceItem slice, boolean isHeader, int rowIndex,
+            SliceView.SliceObserver observer) {
+        // Do nothing
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SliceLiveData.java b/slices/view/src/main/java/androidx/app/slice/widget/SliceLiveData.java
new file mode 100644
index 0000000..346e9c3
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceLiveData.java
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+package androidx.app.slice.widget;
+
+import android.arch.lifecycle.LiveData;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpec;
+import androidx.app.slice.SliceSpecs;
+
+/**
+ * Class with factory methods for creating LiveData that observes slices.
+ *
+ * @see #fromUri(Context, Uri)
+ * @see LiveData
+ */
+public final class SliceLiveData {
+
+    private static final List<SliceSpec> SUPPORTED_SPECS = Arrays.asList(SliceSpecs.BASIC,
+            SliceSpecs.LIST);
+
+    /**
+     * Turns a slice Uri into slice content.
+     *
+     * @param context Context to be used.
+     * @param uri The URI to a slice provider
+     * @return The Slice provided by the app or null if none is given.
+     * @see Slice
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static @Nullable Slice bindSlice(Context context, @NonNull Uri uri) {
+        return Slice.bindSlice(context, uri, SUPPORTED_SPECS);
+    }
+
+
+    /**
+     * Turns a slice intent into slice content. Expects an explicit intent. If there is no
+     * {@link ContentProvider} associated with the given intent this will throw
+     * {@link IllegalArgumentException}.
+     *
+     * @param context The context to use.
+     * @param intent The intent associated with a slice.
+     * @return The Slice provided by the app or null if none is given.
+     * @see Slice
+     * @see androidx.app.slice.SliceProvider#onMapIntentToUri(Intent)
+     * @see Intent
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
+        return Slice.bindSlice(context, intent, SUPPORTED_SPECS);
+    }
+
+    /**
+     * Produces an {@link LiveData} that tracks a Slice for a given Uri. To use
+     * this method your app must have the permission to the slice Uri or hold
+     * {@link android.Manifest.permission#BIND_SLICE}).
+     */
+    public static LiveData<Slice> fromUri(Context context, Uri uri) {
+        return new SliceLiveDataImpl(context.getApplicationContext(), uri);
+    }
+
+    /**
+     * Produces an {@link LiveData} that tracks a Slice for a given Intent. To use
+     * this method your app must have the permission to the slice Uri or hold
+     * {@link android.Manifest.permission#BIND_SLICE}).
+     */
+    public static LiveData<Slice> fromIntent(@NonNull Context context, @NonNull Intent intent) {
+        return new SliceLiveDataImpl(context.getApplicationContext(), intent);
+    }
+
+    private static class SliceLiveDataImpl extends LiveData<Slice> {
+        private final Context mContext;
+        private final Intent mIntent;
+        private Uri mUri;
+
+        private SliceLiveDataImpl(Context context, Uri uri) {
+            super();
+            mContext = context;
+            mUri = uri;
+            mIntent = null;
+            // TODO: Check if uri points at a Slice?
+        }
+
+        private SliceLiveDataImpl(Context context, Intent intent) {
+            super();
+            mContext = context;
+            mUri = null;
+            mIntent = intent;
+        }
+
+        @Override
+        protected void onActive() {
+            AsyncTask.execute(mUpdateSlice);
+            if (mUri != null) {
+                mContext.getContentResolver().registerContentObserver(mUri, false, mObserver);
+            }
+        }
+
+        @Override
+        protected void onInactive() {
+            if (mUri != null) {
+                mContext.getContentResolver().unregisterContentObserver(mObserver);
+            }
+        }
+
+        private final Runnable mUpdateSlice = new Runnable() {
+            @Override
+            public void run() {
+                Slice s = mUri != null ? Slice.bindSlice(mContext, mUri, SUPPORTED_SPECS)
+                        : Slice.bindSlice(mContext, mIntent, SUPPORTED_SPECS);
+                if (mUri == null && s != null) {
+                    mContext.getContentResolver().registerContentObserver(s.getUri(),
+                            false, mObserver);
+                    mUri = s.getUri();
+                }
+                postValue(s);
+            }
+        };
+
+        private final ContentObserver mObserver = new ContentObserver(new Handler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                AsyncTask.execute(mUpdateSlice);
+            }
+        };
+    }
+}
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
new file mode 100644
index 0000000..b623380
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
@@ -0,0 +1,396 @@
+/*
+ * 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.
+ */
+
+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.SUBTYPE_COLOR;
+import static android.app.slice.SliceItem.FORMAT_INT;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+
+import android.arch.lifecycle.Observer;
+import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.SliceSpec;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
+ * able to present slice content in a templated format outside of the associated app. The way this
+ * content is displayed depends on the structure of the slice, the hints associated with the
+ * content, and the mode that SliceView is configured for. The modes that SliceView supports are:
+ * <ul>
+ * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main
+ * content or action associated with the slice.</li>
+ * <li><b>Small</b>: The small format has a restricted height and can present a single
+ * {@link SliceItem} or a limited collection of items.</li>
+ * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is
+ * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can
+ * comfortably fit.</li>
+ * </ul>
+ * <p>
+ * When constructing a slice, the contents of it can be annotated with hints, these provide the OS
+ * with some information on how the content should be displayed. For example, text annotated with
+ * {@link android.app.slice.Slice#HINT_TITLE} would be placed in the title position of a template.
+ * A slice annotated with {@link android.app.slice.Slice#HINT_LIST} would present the child items
+ * of that slice in a list.
+ * <p>
+ * Example usage:
+ *
+ * <pre class="prettyprint">
+ * SliceView v = new SliceView(getContext());
+ * v.setMode(desiredMode);
+ * LiveData<Slice> liveData = SliceLiveData.fromUri(sliceUri);
+ * liveData.observe(lifecycleOwner, v);
+ * </pre>
+ * @see SliceLiveData
+ */
+public class SliceView extends ViewGroup implements Observer<Slice> {
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static List<SliceSpec> SUPPORTED_SPECS = Arrays.asList(
+    );
+
+    private static final String TAG = "SliceView";
+
+    /**
+     * Implement this interface to be notified of interactions with the slice displayed
+     * in this view.
+     * @hide
+     * @see EventInfo
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public interface SliceObserver {
+        /**
+         * Called when an interaction has occurred with an element in this view.
+         * @param info the type of event that occurred.
+         * @param item the specific item within the {@link Slice} that was interacted with.
+         */
+        void onSliceAction(@NonNull EventInfo info, @NonNull SliceItem item);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+            MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+    })
+    public @interface SliceMode {}
+
+    /**
+     * Mode indicating this slice should be presented in small template format.
+     */
+    public static final int MODE_SMALL       = 1;
+    /**
+     * Mode indicating this slice should be presented in large template format.
+     */
+    public static final int MODE_LARGE       = 2;
+    /**
+     * Mode indicating this slice should be presented as an icon. A shortcut requires an intent,
+     * icon, and label. This can be indicated by using {@link android.app.slice.Slice#HINT_TITLE}
+     * on an action in a slice.
+     */
+    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 SliceChildView mCurrentView;
+    private Slice mCurrentSlice;
+    private final ActionRow mActions;
+    private final int mShortcutSize;
+    private SliceObserver mSliceObserver;
+
+    private boolean mShowActions = true;
+    private boolean mIsScrollable = true;
+
+    private int mThemeTintColor = -1;
+    private AttributeSet mAttrs;
+
+    public SliceView(Context context) {
+        this(context, null);
+    }
+
+    public SliceView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mAttrs = attrs;
+        mActions = new ActionRow(getContext(), true);
+        mActions.setBackground(new ColorDrawable(0xffeeeeee));
+        mCurrentView = new LargeTemplateView(getContext());
+        addView(mCurrentView.getView(), getChildLp(mCurrentView.getView()));
+        addView(mActions, getChildLp(mActions));
+        mShortcutSize = getContext().getResources()
+                .getDimensionPixelSize(R.dimen.abc_slice_shortcut_size);
+    }
+
+    @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
+            childWidth = mShortcutSize;
+            width = mShortcutSize;
+        }
+        final int left = getPaddingLeft();
+        final int top = getPaddingTop();
+        final int right = getPaddingRight();
+        final int bot = getPaddingBottom();
+
+        // Measure the children without the padding
+        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 = mActions.getVisibility() != View.GONE
+                ? mActions.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);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        View v = mCurrentView.getView();
+        final int left = getPaddingLeft();
+        final int top = getPaddingTop();
+        final int right = getPaddingRight();
+        final int bottom = getPaddingBottom();
+        v.layout(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
+        if (mActions.getVisibility() != View.GONE) {
+            mActions.layout(left,
+                    top + v.getMeasuredHeight() + bottom,
+                    left + mActions.getMeasuredWidth() + right,
+                    top + v.getMeasuredHeight() + bottom + mActions.getMeasuredHeight());
+        }
+    }
+
+    @Override
+    public void onChanged(@Nullable Slice slice) {
+        setSlice(slice);
+    }
+
+    /**
+     * Populates this view to the provided {@link Slice}.
+     *
+     * This will not update automatically if the slice content changes, for live
+     * content see {@link SliceLiveData}.
+     */
+    public void setSlice(@Nullable Slice slice) {
+        mCurrentSlice = slice;
+        reinflate();
+    }
+
+    /**
+     * Set the mode this view should present in.
+     */
+    public void setMode(@SliceMode int mode) {
+        setMode(mode, false /* animate */);
+    }
+
+    /**
+     * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}.
+     */
+    public void setScrollable(boolean isScrollable) {
+        mIsScrollable = isScrollable;
+        reinflate();
+    }
+
+    /**
+     * Sets the observer to notify when an interaction events occur on the view.
+     * @hide
+     * @see EventInfo
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setSliceObserver(@Nullable SliceObserver observer) {
+        mSliceObserver = observer;
+        mCurrentView.setSliceObserver(mSliceObserver);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setMode(@SliceMode int mode, boolean animate) {
+        if (animate) {
+            Log.e(TAG, "Animation not supported yet");
+        }
+        mMode = mode;
+        reinflate();
+    }
+
+    /**
+     * @return the mode this view is presenting in.
+     */
+    public @SliceMode int getMode() {
+        if (mMode == MODE_AUTO) {
+            return MODE_LARGE;
+        }
+        return mMode;
+    }
+
+    /**
+     * @hide
+     *
+     * Whether this view should show a row of actions with it.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setShowActionRow(boolean show) {
+        mShowActions = show;
+        reinflate();
+    }
+
+    private SliceChildView createView(int mode) {
+        switch (mode) {
+            case MODE_SHORTCUT:
+                return new ShortcutView(getContext());
+            case MODE_SMALL:
+                // Check if it's horizontal and use a grid instead
+                if (SliceQuery.hasHints(mCurrentSlice, HINT_HORIZONTAL)) {
+                    return new GridRowView(getContext());
+                } else {
+                    return new RowView(getContext());
+                }
+        }
+        return new LargeTemplateView(getContext());
+    }
+
+    private void reinflate() {
+        if (mCurrentSlice == null) {
+            mCurrentView.resetView();
+            return;
+        }
+        // TODO: Smarter mapping here from one state to the next.
+        int mode = getMode();
+        if (mMode == mCurrentView.getMode()) {
+            mCurrentView.setSlice(mCurrentSlice);
+        } else {
+            removeAllViews();
+            mCurrentView = createView(mode);
+            if (mSliceObserver != null) {
+                mCurrentView.setSliceObserver(mSliceObserver);
+            }
+            addView(mCurrentView.getView(), getChildLp(mCurrentView.getView()));
+            addView(mActions, getChildLp(mActions));
+        }
+        // Scrolling
+        if (mode == MODE_LARGE) {
+            ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable);
+        }
+        // Styles
+        mCurrentView.setStyle(mAttrs);
+        // Set the slice
+        SliceItem actionRow = SliceQuery.find(mCurrentSlice, FORMAT_SLICE,
+                HINT_ACTIONS,
+                null);
+        List<SliceItem> items = mCurrentSlice.getItems();
+        if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
+            mCurrentView.getView().setVisibility(View.VISIBLE);
+            mCurrentView.setSlice(mCurrentSlice);
+        } else {
+            mCurrentView.getView().setVisibility(View.GONE);
+        }
+        // Deal with actions
+        boolean showActions = mShowActions && actionRow != null
+                && mode != MODE_SHORTCUT;
+        if (showActions) {
+            mActions.setActions(actionRow, getTintColor());
+            mActions.setVisibility(View.VISIBLE);
+        } else {
+            mActions.setVisibility(View.GONE);
+        }
+    }
+
+    private int getTintColor() {
+        if (mThemeTintColor != -1) {
+            // Theme has specified a color, use that
+            return mThemeTintColor;
+        } else {
+            final SliceItem colorItem = SliceQuery.findSubtype(
+                    mCurrentSlice, FORMAT_INT, SUBTYPE_COLOR);
+            return colorItem != null
+                    ? colorItem.getInt()
+                    : SliceViewUtil.getColorAccent(getContext());
+        }
+    }
+
+    private LayoutParams getChildLp(View child) {
+        if (child instanceof ShortcutView) {
+            return new LayoutParams(mShortcutSize, mShortcutSize);
+        } else {
+            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        }
+    }
+
+    /**
+     * @return String representation of the provided mode.
+     * @hide
+     */
+    @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:
+                return "MODE SMALL";
+            case MODE_LARGE:
+                return "MODE LARGE";
+            default:
+                return "unknown mode: " + mode;
+        }
+    }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SliceViewUtil.java b/slices/view/src/main/java/androidx/app/slice/widget/SliceViewUtil.java
new file mode 100644
index 0000000..12fe7c4
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceViewUtil.java
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+
+package androidx.app.slice.widget;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.text.format.DateUtils;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.Calendar;
+
+/**
+ * A bunch of utilities for slice UI.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(23)
+public class SliceViewUtil {
+
+    /**
+     */
+    @ColorInt
+    public static int getColorAccent(@NonNull Context context) {
+        return getColorAttr(context, android.R.attr.colorAccent);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getColorError(@NonNull Context context) {
+        return getColorAttr(context, android.R.attr.colorError);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getDefaultColor(@NonNull Context context, int resId) {
+        final ColorStateList list = context.getResources().getColorStateList(resId,
+                context.getTheme());
+
+        return list.getDefaultColor();
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getDisabled(@NonNull Context context, int inputColor) {
+        return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int applyAlphaAttr(@NonNull Context context, @AttrRes int attr, int inputColor) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        float alpha = ta.getFloat(0, 0);
+        ta.recycle();
+        return applyAlpha(alpha, inputColor);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int applyAlpha(float alpha, int inputColor) {
+        alpha *= Color.alpha(inputColor);
+        return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+                Color.blue(inputColor));
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getColorAttr(@NonNull Context context, @AttrRes int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        @ColorInt int colorAccent = ta.getColor(0, 0);
+        ta.recycle();
+        return colorAccent;
+    }
+
+    /**
+     */
+    public static int getThemeAttr(@NonNull Context context, @AttrRes int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        int theme = ta.getResourceId(0, 0);
+        ta.recycle();
+        return theme;
+    }
+
+    /**
+     */
+    public static Drawable getDrawable(@NonNull Context context, @AttrRes int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        Drawable drawable = ta.getDrawable(0);
+        ta.recycle();
+        return drawable;
+    }
+
+    /**
+     */
+    public static Icon createIconFromDrawable(Drawable d) {
+        if (d instanceof BitmapDrawable) {
+            return Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+        }
+        Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
+                Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(b);
+        d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        d.draw(canvas);
+        return Icon.createWithBitmap(b);
+    }
+
+    /**
+     */
+    @TargetApi(28)
+    public static void createCircledIcon(@NonNull Context context, int iconSizePx,
+            Icon icon, boolean isLarge, ViewGroup parent) {
+        ImageView v = new ImageView(context);
+        v.setImageIcon(icon);
+        parent.addView(v);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        if (isLarge) {
+            // XXX better way to convert from icon -> bitmap or crop an icon (?)
+            Bitmap iconBm = Bitmap.createBitmap(iconSizePx, iconSizePx, Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            v.layout(0, 0, iconSizePx, iconSizePx);
+            v.draw(iconCanvas);
+            v.setImageBitmap(getCircularBitmap(iconBm));
+        } else {
+            v.setColorFilter(Color.WHITE);
+        }
+        lp.width = iconSizePx;
+        lp.height = iconSizePx;
+        lp.gravity = Gravity.CENTER;
+    }
+
+    /**
+     */
+    public static @NonNull Bitmap getCircularBitmap(Bitmap bitmap) {
+        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
+                bitmap.getHeight(), Config.ARGB_8888);
+        Canvas canvas = new Canvas(output);
+        final Paint paint = new Paint();
+        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        paint.setAntiAlias(true);
+        canvas.drawARGB(0, 0, 0, 0);
+        canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
+                bitmap.getWidth() / 2, paint);
+        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+        canvas.drawBitmap(bitmap, rect, rect, paint);
+        return output;
+    }
+
+    /**
+     */
+    public static CharSequence getRelativeTimeString(long time) {
+        return DateUtils.getRelativeTimeSpanString(time, Calendar.getInstance().getTimeInMillis(),
+                DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
+    }
+}
diff --git a/slices/view/src/main/res/drawable/abc_ic_slice_send.xml b/slices/view/src/main/res/drawable/abc_ic_slice_send.xml
new file mode 100644
index 0000000..9c18ac8
--- /dev/null
+++ b/slices/view/src/main/res/drawable/abc_ic_slice_send.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M4.02,42.0L46.0,24.0 4.02,6.0 4.0,20.0l30.0,4.0 -30.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/slices/view/src/main/res/drawable/abc_slice_remote_input_bg.xml b/slices/view/src/main/res/drawable/abc_slice_remote_input_bg.xml
new file mode 100644
index 0000000..64ac7bf
--- /dev/null
+++ b/slices/view/src/main/res/drawable/abc_slice_remote_input_bg.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#ff6c6c6c" />
+    <corners
+        android:bottomRightRadius="16dp"
+        android:bottomLeftRadius="16dp"/>
+</shape>
diff --git a/slices/view/src/main/res/drawable/abc_slice_ripple_drawable.xml b/slices/view/src/main/res/drawable/abc_slice_ripple_drawable.xml
new file mode 100644
index 0000000..22239f3
--- /dev/null
+++ b/slices/view/src/main/res/drawable/abc_slice_ripple_drawable.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:targetApi="24"
+    android:color="?android:attr/colorControlHighlight" />
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
new file mode 100644
index 0000000..7707dae
--- /dev/null
+++ b/slices/view/src/main/res/layout-v21/abc_slice_small_template.xml
@@ -0,0 +1,89 @@
+<?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
new file mode 100644
index 0000000..890f77d
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_grid.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+<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:gravity="center_vertical"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+</androidx.app.slice.widget.GridRowView>
diff --git a/slices/view/src/main/res/layout/abc_slice_message.xml b/slices/view/src/main/res/layout/abc_slice_message.xml
new file mode 100644
index 0000000..9e1fa62
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_message.xml
@@ -0,0 +1,59 @@
+<?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.
+  -->
+<androidx.app.slice.widget.MessageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="12dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <LinearLayout
+            android:id="@+id/abc_icon_frame"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="-4dp"
+            android:gravity="start|center_vertical"
+            android:orientation="horizontal"
+            android:paddingEnd="12dp"
+            android:paddingTop="4dp"
+            android:paddingBottom="4dp">
+            <!-- TODO: Support text source -->
+            <ImageView
+                android:id="@android:id/icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxWidth="48dp"
+                android:maxHeight="48dp" />
+        </LinearLayout>
+
+        <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/textAppearanceListItem"
+            android:maxLines="10" />
+
+    </LinearLayout>
+</androidx.app.slice.widget.MessageView>
diff --git a/slices/view/src/main/res/layout/abc_slice_message_local.xml b/slices/view/src/main/res/layout/abc_slice_message_local.xml
new file mode 100644
index 0000000..d35bd60
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_message_local.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+<androidx.app.slice.widget.MessageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical|end"
+    android:paddingTop="12dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <TextView android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignStart="@android:id/title"
+        android:layout_gravity="end"
+        android:gravity="end"
+        android:padding="8dp"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:background="#ffeeeeee"
+        android:maxLines="10" />
+
+</androidx.app.slice.widget.MessageView>
diff --git a/slices/view/src/main/res/layout/abc_slice_remote_input.xml b/slices/view/src/main/res/layout/abc_slice_remote_input.xml
new file mode 100644
index 0000000..293c95a
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_remote_input.xml
@@ -0,0 +1,77 @@
+<?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 -->
+<androidx.app.slice.widget.RemoteInputView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/remote_input"
+        android:background="@drawable/abc_slice_remote_input_bg"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent">
+
+    <view class="androidx.app.slice.widget.RemoteInputView$RemoteEditText"
+            android:id="@+id/remote_input_text"
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:paddingTop="2dp"
+            android:paddingBottom="4dp"
+            android:paddingStart="16dp"
+            android:paddingEnd="12dp"
+            android:gravity="start|center_vertical"
+            android:textAppearance="?android:attr/textAppearance"
+            android:textColor="#FFFFFFFF"
+            android:textColorHint="#99ffffff"
+            android:textSize="16sp"
+            android:background="@null"
+            android:singleLine="true"
+            android:ellipsize="start"
+            android:inputType="textShortMessage|textAutoCorrect|textCapSentences"
+            android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
+
+    <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical">
+
+        <ImageButton
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:paddingStart="12dp"
+                android:paddingEnd="24dp"
+                android:paddingTop="16dp"
+                android:paddingBottom="16dp"
+                android:id="@+id/remote_input_send"
+                android:src="@drawable/abc_ic_slice_send"
+                android:tint="#FFFFFF"
+                android:tintMode="src_in"
+                android:background="@drawable/abc_slice_ripple_drawable" />
+
+        <ProgressBar
+                android:id="@+id/remote_input_progress"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_marginEnd="6dp"
+                android:layout_gravity="center"
+                android:visibility="invisible"
+                android:indeterminate="true"
+                style="?android:attr/progressBarStyleSmall" />
+
+    </FrameLayout>
+
+</androidx.app.slice.widget.RemoteInputView>
\ No newline at end of file
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
new file mode 100644
index 0000000..b446ddd
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_secondary_text.xml
@@ -0,0 +1,26 @@
+<?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 -->
+<TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondary"
+        android:gravity="center"
+        android:layout_height="wrap_content"
+        android:padding="4dp"
+        android:layout_width="match_parent" />
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
new file mode 100644
index 0000000..706ded6
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_small_template.xml
@@ -0,0 +1,89 @@
+<?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="8dp"/>
+
+    <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:textAppearance="?android:attr/textAppearanceListItem"
+            android:textColor="?android:attr/textColorPrimary"
+            android:maxLines="2"/>
+
+        <TextView android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignStart="@android:id/title"
+            android:textAppearance="?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:paddingStart="8dp"
+        android:gravity="end|center_vertical"
+        android:orientation="horizontal" />
+
+</LinearLayout>
diff --git a/slices/view/src/main/res/layout/abc_slice_title.xml b/slices/view/src/main/res/layout/abc_slice_title.xml
new file mode 100644
index 0000000..e1bdf03
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_title.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+
+<TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorPrimary"
+        android:gravity="center"
+        android:layout_height="wrap_content"
+        android:padding="4dp"
+        android:layout_width="match_parent" />
diff --git a/slices/view/src/main/res/values/dimens.xml b/slices/view/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..ff2fb97
--- /dev/null
+++ b/slices/view/src/main/res/values/dimens.xml
@@ -0,0 +1,48 @@
+<?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.
+  -->
+
+<resources>
+    <!-- General -->
+    <!-- Size of normal icons / images 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>
+    <!-- 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 -->
+    <dimen name="abc_slice_large_height">240dp</dimen>
+
+    <!-- Row view sizes-->
+    <!-- Min height of row view; default size if one line of text -->
+    <dimen name="abc_slice_row_min_height">48dp</dimen>
+    <!-- Max height of row view; default size if two lines of text -->
+    <dimen name="abc_slice_row_max_height">60dp</dimen>
+    <!-- Min height of a row showing an input field that is active -->
+    <dimen name="abc_slice_row_active_input_height">120dp</dimen>
+
+    <!-- Grid view sizes-->
+    <!-- Height of a grid row displaying only 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>
+</resources>
\ No newline at end of file
diff --git a/slices/view/src/main/res/values/strings.xml b/slices/view/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b72c986
--- /dev/null
+++ b/slices/view/src/main/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Format string for indicating there is more content in a slice view -->
+    <string name="abc_slice_more_content">+ <xliff:g id="number" example="5">%1$d</xliff:g></string>
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res-public/values/public_attrs.xml b/v7/appcompat/res-public/values/public_attrs.xml
index 8c9f9e9..3b811ec 100644
--- a/v7/appcompat/res-public/values/public_attrs.xml
+++ b/v7/appcompat/res-public/values/public_attrs.xml
@@ -102,6 +102,7 @@
      <public type="attr" name="customNavigationLayout"/>
      <public type="attr" name="dialogPreferredPadding"/>
      <public type="attr" name="dialogTheme"/>
+     <public type="attr" name="dialogCornerRadius"/>
      <public type="attr" name="displayOptions"/>
      <public type="attr" name="divider"/>
      <public type="attr" name="dividerHorizontal"/>
diff --git a/v7/appcompat/res/drawable-v21/abc_dialog_material_background.xml b/v7/appcompat/res/drawable-v21/abc_dialog_material_background.xml
new file mode 100644
index 0000000..7ef438b
--- /dev/null
+++ b/v7/appcompat/res/drawable-v21/abc_dialog_material_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetLeft="16dp"
+       android:insetTop="16dp"
+       android:insetRight="16dp"
+       android:insetBottom="16dp">
+    <shape android:shape="rectangle">
+        <corners android:radius="?attr/dialogCornerRadius" />
+        <solid android:color="@android:color/white" />
+    </shape>
+</inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_dialog_material_background.xml b/v7/appcompat/res/drawable/abc_dialog_material_background.xml
index 18560fc..978565b 100644
--- a/v7/appcompat/res/drawable/abc_dialog_material_background.xml
+++ b/v7/appcompat/res/drawable/abc_dialog_material_background.xml
@@ -20,7 +20,7 @@
        android:insetRight="16dp"
        android:insetBottom="16dp">
     <shape android:shape="rectangle">
-        <corners android:radius="2dp" />
+        <corners android:radius="@dimen/abc_dialog_corner_radius_material" />
         <solid android:color="@android:color/white" />
     </shape>
 </inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-bs/strings.xml b/v7/appcompat/res/values-bs/strings.xml
index 3687875..07d1411 100644
--- a/v7/appcompat/res/values-bs/strings.xml
+++ b/v7/appcompat/res/values-bs/strings.xml
@@ -29,8 +29,8 @@
     <string name="abc_searchview_description_voice" msgid="893419373245838918">"Glasovno pretraživanje"</string>
     <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Odaberite aplikaciju"</string>
     <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Prikaži sve"</string>
-    <string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"Podijeli koristeći aplikaciju <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
-    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Podijeli sa"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"Dijeli koristeći aplikaciju <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Dijeli sa"</string>
     <string name="abc_capital_on" msgid="3405795526292276155">"UKLJUČI"</string>
     <string name="abc_capital_off" msgid="121134116657445385">"ISKLJUČI"</string>
     <string name="search_menu_title" msgid="146198913615257606">"Pretraži"</string>
diff --git a/v7/appcompat/res/values-hi/strings.xml b/v7/appcompat/res/values-hi/strings.xml
index 3a393c7..a31ab90 100644
--- a/v7/appcompat/res/values-hi/strings.xml
+++ b/v7/appcompat/res/values-hi/strings.xml
@@ -29,8 +29,8 @@
     <string name="abc_searchview_description_voice" msgid="893419373245838918">"आवाज़ सर्च"</string>
     <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"कोई एप्‍लिकेशन चुनें"</string>
     <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"सभी देखें"</string>
-    <string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> के साथ साझा करें"</string>
-    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"इसके द्वारा साझा करें"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> के साथ शेयर करें"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"इसके साथ शेयर करें"</string>
     <string name="abc_capital_on" msgid="3405795526292276155">"चालू"</string>
     <string name="abc_capital_off" msgid="121134116657445385">"बंद"</string>
     <string name="search_menu_title" msgid="146198913615257606">"सर्च"</string>
diff --git a/v7/appcompat/res/values-ne/strings.xml b/v7/appcompat/res/values-ne/strings.xml
index 0d23bd7..96b1042 100644
--- a/v7/appcompat/res/values-ne/strings.xml
+++ b/v7/appcompat/res/values-ne/strings.xml
@@ -29,7 +29,7 @@
     <string name="abc_searchview_description_voice" msgid="893419373245838918">"भ्वाइस खोजी"</string>
     <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"एउटा अनुप्रयोग छान्नुहोस्"</string>
     <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"सबै हेर्नुहोस्"</string>
-    <string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> सँग आदान-प्रदान गर्नुहोस्"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> सँग आदान प्रदान गर्नुहोस्"</string>
     <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"साझेदारी गर्नुहोस्..."</string>
     <string name="abc_capital_on" msgid="3405795526292276155">"सक्रिय गर्नुहोस्"</string>
     <string name="abc_capital_off" msgid="121134116657445385">"निष्क्रिय पार्नुहोस्"</string>
diff --git a/v7/appcompat/res/values-ta/strings.xml b/v7/appcompat/res/values-ta/strings.xml
index 4a2ad2f..971a3db 100644
--- a/v7/appcompat/res/values-ta/strings.xml
+++ b/v7/appcompat/res/values-ta/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="abc_action_mode_done" msgid="4076576682505996667">"முடிந்தது"</string>
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"முகப்பிற்கு வழிசெலுத்து"</string>
-    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"மேலே வழிசெலுத்து"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"மேலே செல்"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"மேலும் விருப்பங்கள்"</string>
     <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"சுருக்கு"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"தேடு"</string>
diff --git a/v7/appcompat/res/values-uz/strings.xml b/v7/appcompat/res/values-uz/strings.xml
index 632e0b9..0417cba 100644
--- a/v7/appcompat/res/values-uz/strings.xml
+++ b/v7/appcompat/res/values-uz/strings.xml
@@ -28,7 +28,7 @@
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"So‘rov yaratish"</string>
     <string name="abc_searchview_description_voice" msgid="893419373245838918">"Ovozli qidiruv"</string>
     <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Dastur tanlang"</string>
-    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Barchasini ko‘rish"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Hammasi"</string>
     <string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> orqali ulashish"</string>
     <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Ruxsat berish"</string>
     <string name="abc_capital_on" msgid="3405795526292276155">"YONIQ"</string>
diff --git a/v7/appcompat/res/values-v28/themes_base.xml b/v7/appcompat/res/values-v28/themes_base.xml
new file mode 100644
index 0000000..7dea6d5
--- /dev/null
+++ b/v7/appcompat/res/values-v28/themes_base.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  -->
+
+<resources>
+
+    <style name="Base.Theme.AppCompat" parent="Base.V28.Theme.AppCompat" />
+    <style name="Base.Theme.AppCompat.Light" parent="Base.V28.Theme.AppCompat.Light" />
+
+    <style name="Base.V28.Theme.AppCompat" parent="Base.V26.Theme.AppCompat">
+        <!-- We can use the platform styles on API 28+ -->
+        <item name="dialogCornerRadius">?android:attr/dialogCornerRadius</item>
+    </style>
+
+    <style name="Base.V28.Theme.AppCompat.Light" parent="Base.V26.Theme.AppCompat.Light">
+        <!-- We can use the platform styles on API 28+ -->
+        <item name="dialogCornerRadius">?android:attr/dialogCornerRadius</item>
+    </style>
+
+</resources>
diff --git a/v7/appcompat/res/values-vi/strings.xml b/v7/appcompat/res/values-vi/strings.xml
index 9587bed..4560b4b 100644
--- a/v7/appcompat/res/values-vi/strings.xml
+++ b/v7/appcompat/res/values-vi/strings.xml
@@ -19,7 +19,7 @@
     <string name="abc_action_mode_done" msgid="4076576682505996667">"Xong"</string>
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Điều hướng về trang chủ"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Điều hướng lên trên"</string>
-    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Thêm tùy chọn"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Tùy chọn khác"</string>
     <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Thu gọn"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Tìm kiếm"</string>
     <string name="abc_search_hint" msgid="7723749260725869598">"Tìm kiếm…"</string>
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index c48b7ba..7d26a32 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -189,6 +189,8 @@
         <attr name="dialogPreferredPadding" format="dimension" />
         <!-- The list divider used in alert dialogs. -->
         <attr name="listDividerAlertDialog" format="reference" />
+        <!-- Preferred corner radius of dialogs. -->
+        <attr name="dialogCornerRadius" format="dimension" />
 
         <!-- =================== -->
         <!-- Other widget styles -->
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index 5bcd4b4..6357caa 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -75,6 +75,7 @@
     <dimen name="abc_dialog_title_divider_material">8dp</dimen>
     <dimen name="abc_dialog_list_padding_top_no_title">8dp</dimen>
     <dimen name="abc_dialog_list_padding_bottom_no_buttons">8dp</dimen>
+    <dimen name="abc_dialog_corner_radius_material">2dp</dimen>
 
     <!-- Dialog button bar height -->
     <dimen name="abc_alert_dialog_button_bar_height">48dp</dimen>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index a9acfce..6b4461f 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -265,6 +265,7 @@
         <!-- Dialog attributes -->
         <item name="dialogTheme">@style/ThemeOverlay.AppCompat.Dialog</item>
         <item name="dialogPreferredPadding">@dimen/abc_dialog_padding_material</item>
+        <item name="dialogCornerRadius">@dimen/abc_dialog_corner_radius_material</item>
 
         <item name="alertDialogTheme">@style/ThemeOverlay.AppCompat.Dialog.Alert</item>
         <item name="alertDialogStyle">@style/AlertDialog.AppCompat</item>
@@ -435,6 +436,7 @@
         <!-- Dialog attributes -->
         <item name="dialogTheme">@style/ThemeOverlay.AppCompat.Dialog</item>
         <item name="dialogPreferredPadding">@dimen/abc_dialog_padding_material</item>
+        <item name="dialogCornerRadius">@dimen/abc_dialog_corner_radius_material</item>
 
         <item name="alertDialogTheme">@style/ThemeOverlay.AppCompat.Dialog.Alert</item>
         <item name="alertDialogStyle">@style/AlertDialog.AppCompat.Light</item>
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java
index 6831fcb..5921937 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatEditText.java
@@ -25,8 +25,10 @@
 import android.support.annotation.DrawableRes;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.v4.os.BuildCompat;
 import android.support.v4.view.TintableBackgroundView;
 import android.support.v7.appcompat.R;
+import android.text.Editable;
 import android.util.AttributeSet;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -72,6 +74,21 @@
     }
 
     @Override
+    public Editable getText() {
+        if (BuildCompat.isAtLeastP()) {
+            return super.getText();
+        }
+        // A bug pre-P makes getText() crash if called before the first setText due to a cast.
+        Editable text = super.getEditableText();
+        if (text != null) {
+            return text;
+        }
+        // The empty String is the value set during the constructor before the first setText call.
+        super.setText("", BufferType.EDITABLE);
+        return super.getText();
+    }
+
+    @Override
     public void setBackgroundResource(@DrawableRes int resId) {
         super.setBackgroundResource(resId);
         if (mBackgroundTintHelper != null) {
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java
index 3d3c300..8de44e6 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompatHandler.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.support.annotation.RestrictTo;
 import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewConfigurationCompat;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -46,6 +47,7 @@
 
     private final View mAnchor;
     private final CharSequence mTooltipText;
+    private final int mHoverSlop;
 
     private final Runnable mShowRunnable = new Runnable() {
         @Override
@@ -82,10 +84,10 @@
     public static void setTooltipText(View view, CharSequence tooltipText) {
         // The code below is not attempting to update the tooltip text
         // for a pending or currently active tooltip, because it may lead
-        // to updating the wrong tooltipin in some rare cases (e.g. when
+        // to updating the wrong tooltip in in some rare cases (e.g. when
         // action menu item views are recycled). Instead, the tooltip is
         // canceled/hidden. This might still be the wrong tooltip,
-        // but hiding wrong tooltip is less disruptive UX.
+        // but hiding a wrong tooltip is less disruptive UX.
         if (sPendingHandler != null && sPendingHandler.mAnchor == view) {
             setPendingHandler(null);
         }
@@ -104,6 +106,9 @@
     private TooltipCompatHandler(View anchor, CharSequence tooltipText) {
         mAnchor = anchor;
         mTooltipText = tooltipText;
+        mHoverSlop = ViewConfigurationCompat.getScaledHoverSlop(
+                ViewConfiguration.get(mAnchor.getContext()));
+        clearAnchorPos();
 
         mAnchor.setOnLongClickListener(this);
         mAnchor.setOnHoverListener(this);
@@ -129,13 +134,12 @@
         }
         switch (event.getAction()) {
             case MotionEvent.ACTION_HOVER_MOVE:
-                if (mAnchor.isEnabled() && mPopup == null) {
-                    mAnchorX = (int) event.getX();
-                    mAnchorY = (int) event.getY();
+                if (mAnchor.isEnabled() && mPopup == null && updateAnchorPos(event)) {
                     setPendingHandler(this);
                 }
                 break;
             case MotionEvent.ACTION_HOVER_EXIT:
+                clearAnchorPos();
                 hide();
                 break;
         }
@@ -188,6 +192,7 @@
             if (mPopup != null) {
                 mPopup.hide();
                 mPopup = null;
+                clearAnchorPos();
                 mAnchor.removeOnAttachStateChangeListener(this);
             } else {
                 Log.e(TAG, "sActiveHandler.mPopup == null");
@@ -216,4 +221,31 @@
     private void cancelPendingShow() {
         mAnchor.removeCallbacks(mShowRunnable);
     }
+
+    /**
+     * Update the anchor position if it significantly (that is by at least mHoverSlope)
+     * different from the previously stored position. Ignoring insignificant changes
+     * filters out the jitter which is typical for such input sources as stylus.
+     *
+     * @return True if the position has been updated.
+     */
+    private boolean updateAnchorPos(MotionEvent event) {
+        final int newAnchorX = (int) event.getX();
+        final int newAnchorY = (int) event.getY();
+        if (Math.abs(newAnchorX - mAnchorX) <= mHoverSlop
+                && Math.abs(newAnchorY - mAnchorY) <= mHoverSlop) {
+            return false;
+        }
+        mAnchorX = newAnchorX;
+        mAnchorY = newAnchorY;
+        return true;
+    }
+
+    /**
+     *  Clear the anchor position to ensure that the next change is considered significant.
+     */
+    private void clearAnchorPos() {
+        mAnchorX = Integer.MAX_VALUE;
+        mAnchorY = Integer.MAX_VALUE;
+    }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatEditTextTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatEditTextTest.java
new file mode 100644
index 0000000..152e7de
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatEditTextTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.support.v7.widget;
+
+import android.content.Context;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.app.AppCompatActivity;
+import android.text.Editable;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link AppCompatEditText} class.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AppCompatEditTextTest {
+    @Rule
+    public final ActivityTestRule<AppCompatActivity> mActivityTestRule =
+            new ActivityTestRule<>(AppCompatActivity.class);
+
+    @Test
+    @UiThreadTest
+    public void testGetTextNonEditable() {
+        // This subclass calls getText before the object is fully constructed. This should not cause
+        // a null pointer exception.
+        GetTextEditText editText = new GetTextEditText(mActivityTestRule.getActivity());
+    }
+
+    private class GetTextEditText extends AppCompatEditText {
+
+        GetTextEditText(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void setText(CharSequence text, BufferType type) {
+            Editable currentText = getText();
+            super.setText(text, type);
+        }
+    }
+}
diff --git a/v7/cardview/Android.mk b/v7/cardview/Android.mk
index 2d9492d..563ea9d 100644
--- a/v7/cardview/Android.mk
+++ b/v7/cardview/Android.mk
@@ -22,6 +22,7 @@
 # in their makefiles to include the resources in their package.
 include $(CLEAR_VARS)
 LOCAL_USE_AAPT2 := true
+LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE := android-support-v7-cardview
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := \
diff --git a/v7/mediarouter/res/values-ar/strings.xml b/v7/mediarouter/res/values-ar/strings.xml
index 2cfe9e4..864fb91 100644
--- a/v7/mediarouter/res/values-ar/strings.xml
+++ b/v7/mediarouter/res/values-ar/strings.xml
@@ -34,7 +34,7 @@
     <string name="mr_controller_collapse_group" msgid="7924809056904240926">"تصغير"</string>
     <string name="mr_controller_album_art" msgid="6422801843540543585">"صورة الألبوم"</string>
     <string name="mr_controller_volume_slider" msgid="2361785992211841709">"شريط تمرير مستوى الصوت"</string>
-    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"لم يتم اختيار أية وسائط"</string>
-    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"لا تتوفر أية معلومات"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"لم يتم اختيار أي وسائط"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"لا تتوفر أي معلومات"</string>
     <string name="mr_controller_casting_screen" msgid="4868457957151124867">"جارٍ إرسال الشاشة"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ca/strings.xml b/v7/mediarouter/res/values-ca/strings.xml
index a5e5883..7e01048 100644
--- a/v7/mediarouter/res/values-ca/strings.xml
+++ b/v7/mediarouter/res/values-ca/strings.xml
@@ -34,7 +34,7 @@
     <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Replega"</string>
     <string name="mr_controller_album_art" msgid="6422801843540543585">"Imatge de l\'àlbum"</string>
     <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Control lliscant de volum"</string>
-    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No s\'ha seleccionat cap fitxer multimèdia"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No s\'han seleccionat fitxers multimèdia"</string>
     <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No hi ha informació disponible"</string>
     <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emissió de pantalla"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-da/strings.xml b/v7/mediarouter/res/values-da/strings.xml
index 44da892..d280d2c 100644
--- a/v7/mediarouter/res/values-da/strings.xml
+++ b/v7/mediarouter/res/values-da/strings.xml
@@ -34,7 +34,7 @@
     <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skjul"</string>
     <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumgrafik"</string>
     <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Lydstyrkeskyder"</string>
-    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Der er ikke valgt nogen medier"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ingen medier er markeret"</string>
     <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Der er ingen tilgængelige oplysninger"</string>
     <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skærmen castes"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-eu/strings.xml b/v7/mediarouter/res/values-eu/strings.xml
index f9b9f8d..11b1d00 100644
--- a/v7/mediarouter/res/values-eu/strings.xml
+++ b/v7/mediarouter/res/values-eu/strings.xml
@@ -22,7 +22,7 @@
     <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Igortzeko botoia. Deskonektatuta"</string>
     <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Igortzeko botoia. Konektatzen"</string>
     <string name="mr_cast_button_connected" msgid="5088427771788648085">"Igortzeko botoia. Konektatuta"</string>
-    <string name="mr_chooser_title" msgid="414301941546135990">"Igorri hona"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Igorri hona:"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"Gailuak bilatzen"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"Deskonektatu"</string>
     <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Utzi igortzeari"</string>
diff --git a/v7/mediarouter/res/values-in/strings.xml b/v7/mediarouter/res/values-in/strings.xml
index 5e537af..becb41e 100644
--- a/v7/mediarouter/res/values-in/strings.xml
+++ b/v7/mediarouter/res/values-in/strings.xml
@@ -18,14 +18,14 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"Perangkat"</string>
-    <string name="mr_button_content_description" msgid="3698378085901466129">"Tombol transmisi"</string>
-    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tombol transmisi. Terputus"</string>
-    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tombol transmisi. Menghubungkan"</string>
-    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tombol transmisi. Terhubung"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Tombol Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tombol Cast. Terputus"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tombol Cast. Menghubungkan"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tombol Cast. Terhubung"</string>
     <string name="mr_chooser_title" msgid="414301941546135990">"Transmisikan ke"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"Mencari perangkat"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"Putuskan sambungan"</string>
-    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Hentikan cast"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Hentikan Transmisi"</string>
     <string name="mr_controller_close_description" msgid="7333862312480583260">"Tutup"</string>
     <string name="mr_controller_play" msgid="683634565969987458">"Putar"</string>
     <string name="mr_controller_pause" msgid="5451884435510905406">"Jeda"</string>
diff --git a/v7/mediarouter/res/values-ml/strings.xml b/v7/mediarouter/res/values-ml/strings.xml
index c7b50be..62258fb 100644
--- a/v7/mediarouter/res/values-ml/strings.xml
+++ b/v7/mediarouter/res/values-ml/strings.xml
@@ -26,7 +26,7 @@
     <string name="mr_chooser_searching" msgid="6349900579507521956">"ഉപകരണങ്ങൾ കണ്ടെത്തുന്നു"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"വിച്ഛേദിക്കുക"</string>
     <string name="mr_controller_stop_casting" msgid="8857886794086583226">"കാസ്റ്റുചെയ്യൽ നിർത്തുക"</string>
-    <string name="mr_controller_close_description" msgid="7333862312480583260">"അടയ്‌ക്കുക"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"അവസാനിപ്പിക്കുക"</string>
     <string name="mr_controller_play" msgid="683634565969987458">"പ്ലേ ചെയ്യുക"</string>
     <string name="mr_controller_pause" msgid="5451884435510905406">"തൽക്കാലം നിർത്തൂ"</string>
     <string name="mr_controller_stop" msgid="735874641921425123">"നിര്‍ത്തുക"</string>
diff --git a/v7/mediarouter/res/values-mr/strings.xml b/v7/mediarouter/res/values-mr/strings.xml
index 27923d1..596b56a 100644
--- a/v7/mediarouter/res/values-mr/strings.xml
+++ b/v7/mediarouter/res/values-mr/strings.xml
@@ -20,7 +20,7 @@
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"डिव्हाइसेस"</string>
     <string name="mr_button_content_description" msgid="3698378085901466129">"कास्ट बटण"</string>
     <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट बटण. डिस्कनेक्ट केले"</string>
-    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट बटण. कनेक्ट करीत आहे"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट बटण. कनेक्ट करत आहे"</string>
     <string name="mr_cast_button_connected" msgid="5088427771788648085">"कास्ट बटण. कनेक्ट केले"</string>
     <string name="mr_chooser_title" msgid="414301941546135990">"यावर कास्ट करा"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"डिव्हाइसेस शोधत आहे"</string>
@@ -36,5 +36,5 @@
     <string name="mr_controller_volume_slider" msgid="2361785992211841709">"व्हॉल्यूम स्लायडर"</string>
     <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"मीडिया निवडला नाही"</string>
     <string name="mr_controller_no_info_available" msgid="5585418471741142924">"कोणतीही माहिती उपलब्ध नाही"</string>
-    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्‍ट करीत आहे"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्‍ट करत आहे"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ta/strings.xml b/v7/mediarouter/res/values-ta/strings.xml
index 99c6172..9888472 100644
--- a/v7/mediarouter/res/values-ta/strings.xml
+++ b/v7/mediarouter/res/values-ta/strings.xml
@@ -22,7 +22,7 @@
     <string name="mr_cast_button_disconnected" msgid="816305490427819240">"அனுப்புதல் பொத்தான். துண்டிக்கப்பட்டது"</string>
     <string name="mr_cast_button_connecting" msgid="2187642765091873834">"அனுப்புதல் பொத்தான். இணைக்கிறது"</string>
     <string name="mr_cast_button_connected" msgid="5088427771788648085">"அனுப்புதல் பொத்தான். இணைக்கப்பட்டது"</string>
-    <string name="mr_chooser_title" msgid="414301941546135990">"இதில் திரையிடு"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"இதற்கு அனுப்பு"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"சாதனங்களைத் தேடுகிறது"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"தொடர்பைத் துண்டி"</string>
     <string name="mr_controller_stop_casting" msgid="8857886794086583226">"அனுப்புவதை நிறுத்து"</string>
diff --git a/v7/mediarouter/res/values-tr/strings.xml b/v7/mediarouter/res/values-tr/strings.xml
index a0eb2e4..8189092 100644
--- a/v7/mediarouter/res/values-tr/strings.xml
+++ b/v7/mediarouter/res/values-tr/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"Cihazlar"</string>
-    <string name="mr_button_content_description" msgid="3698378085901466129">"Yayın düğmesi"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Yayınla düğmesi"</string>
     <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Yayınla düğmesi. Bağlantı kesildi"</string>
     <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Yayınla düğmesi. Bağlanıyor"</string>
     <string name="mr_cast_button_connected" msgid="5088427771788648085">"Yayınla düğmesi. Bağlandı"</string>
diff --git a/v7/preference/res/values/styles.xml b/v7/preference/res/values/styles.xml
index 46351e1..be1797b 100644
--- a/v7/preference/res/values/styles.xml
+++ b/v7/preference/res/values/styles.xml
@@ -84,10 +84,6 @@
 
     <style name="Preference.Material">
         <item name="android:layout">@layout/preference_material</item>
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="singleLineTitle">false</item>
-        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.Information.Material">
@@ -98,16 +94,10 @@
 
     <style name="Preference.Category.Material">
         <item name="android:layout">@layout/preference_category_material</item>
-        <item name="allowDividerAbove">true</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.CheckBoxPreference.Material">
         <item name="android:layout">@layout/preference_material</item>
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.SwitchPreferenceCompat.Material">
@@ -116,10 +106,6 @@
 
     <style name="Preference.SwitchPreference.Material">
         <item name="android:layout">@layout/preference_material</item>
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="singleLineTitle">false</item>
-        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.SeekBarPreference.Material">
@@ -130,31 +116,18 @@
 
     <style name="Preference.PreferenceScreen.Material">
         <item name="android:layout">@layout/preference_material</item>
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.DialogPreference.Material">
         <item name="android:layout">@layout/preference_material</item>
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.DialogPreference.EditTextPreference.Material">
         <item name="android:layout">@layout/preference_material</item>
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="singleLineTitle">false</item>
-        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference.DropDown.Material">
         <item name="android:layout">@layout/preference_dropdown_material</item>
-        <item name="allowDividerAbove">false</item>
-        <item name="allowDividerBelow">true</item>
-        <item name="iconSpaceReserved">true</item>
     </style>
 
     <style name="Preference_TextAppearanceMaterialBody2">
@@ -173,7 +146,6 @@
 
     <style name="PreferenceFragment.Material">
         <item name="android:divider">@drawable/preference_list_divider_material</item>
-        <item name="allowDividerAfterLastItem">false</item>
     </style>
 
     <style name="PreferenceFragmentList.Material">
diff --git a/v7/preference/res/values/themes.xml b/v7/preference/res/values/themes.xml
index 598c24f..1f8f158 100644
--- a/v7/preference/res/values/themes.xml
+++ b/v7/preference/res/values/themes.xml
@@ -51,6 +51,5 @@
         <item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference.Material</item>
         <item name="dropdownPreferenceStyle">@style/Preference.DropDown.Material</item>
         <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item>
-        <item name="android:scrollbars">vertical</item>
     </style>
 </resources>
diff --git a/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
index a287979..88607f8 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
@@ -10108,7 +10108,7 @@
             if (vScroll == 0 && hScroll == 0) {
                 return false;
             }
-            mRecyclerView.scrollBy(hScroll, vScroll);
+            mRecyclerView.smoothScrollBy(hScroll, vScroll);
             return true;
         }