Merge "Add SliceSpec to slices-core"
diff --git a/slices/core/build.gradle b/slices/core/build.gradle
index 4cec017..e2015bb 100644
--- a/slices/core/build.gradle
+++ b/slices/core/build.gradle
@@ -15,8 +15,8 @@
  */
 
 import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryVersions
 import android.support.LibraryGroups
+import android.support.LibraryVersions
 
 plugins {
     id("SupportAndroidLibraryPlugin")
@@ -25,6 +25,8 @@
 dependencies {
     implementation project(":support-annotations")
     implementation project(":appcompat-v7")
+
+    androidTestImplementation(TEST_RUNNER)
 }
 
 android {
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..4bb7e9f
--- /dev/null
+++ b/slices/core/src/androidTest/java/androidx/app/slice/SliceTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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_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.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());
+        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);
+        assertEquals(BASE_URI, s.getUri());
+    }
+
+    @Test
+    public void testSubSlice() {
+        Uri uri = BASE_URI.buildUpon().appendPath("subslice").build();
+        Slice s = Slice.bindSlice(mContext, uri);
+        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);
+        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);
+        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);
+        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);
+    }
+
+    /** Pending updates to androidx version of Slice
+    @Test
+    public void testInt() {
+        Uri uri = BASE_URI.buildUpon().appendPath("int").build();
+        Slice s = Slice.bindSlice(mContext, uri);
+        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);
+        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);
+        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..1031063
--- /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/java/androidx/app/slice/Slice.java b/slices/core/src/main/java/androidx/app/slice/Slice.java
index 8033bb7..bda341d 100644
--- a/slices/core/src/main/java/androidx/app/slice/Slice.java
+++ b/slices/core/src/main/java/androidx/app/slice/Slice.java
@@ -33,6 +33,8 @@
 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;
@@ -52,11 +54,11 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import androidx.app.slice.compat.SliceProviderCompat;
 import androidx.app.slice.core.SliceHints;
-import androidx.app.slice.core.SliceSpecs;
 
 /**
  * A slice is a piece of app content and actions that can be surfaced outside of the app.
@@ -69,6 +71,9 @@
     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
@@ -87,10 +92,12 @@
      * @hide
      */
     @RestrictTo(Scope.LIBRARY)
-    Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+    Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri,
+            SliceSpec spec) {
         mHints = hints;
         mItems = items.toArray(new SliceItem[items.size()]);
         mUri = uri;
+        mSpec = spec;
     }
 
     /**
@@ -107,6 +114,9 @@
             }
         }
         mUri = in.getParcelable(URI);
+        mSpec = in.containsKey(SPEC_TYPE)
+                ? new SliceSpec(in.getString(SPEC_TYPE), in.getInt(SPEC_REVISION))
+                : null;
     }
 
     /**
@@ -122,10 +132,23 @@
         }
         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() {
@@ -162,6 +185,7 @@
         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.
@@ -182,6 +206,16 @@
         }
 
         /**
+         * 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) {
@@ -344,7 +378,7 @@
          * Construct the slice.
          */
         public Slice build() {
-            return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri);
+            return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
         }
     }
 
@@ -387,17 +421,19 @@
      */
     @SuppressWarnings("NewApi")
     public static @Nullable Slice bindSlice(Context context, @NonNull Uri uri) {
+        // TODO: Hide this and only allow binding through SliceView.
         if (BuildCompat.isAtLeastP()) {
-            return callBindSlice(context, uri);
+            return callBindSlice(context, uri, Collections.<SliceSpec>emptyList());
         } else {
-            return SliceProviderCompat.bindSlice(context, uri);
+            return SliceProviderCompat.bindSlice(context, uri, Collections.<SliceSpec>emptyList());
         }
     }
 
     @TargetApi(28)
-    private static Slice callBindSlice(Context context, Uri uri) {
+    private static Slice callBindSlice(Context context, Uri uri,
+            List<SliceSpec> supportedSpecs) {
         return SliceConvert.wrap(android.app.slice.Slice.bindSlice(
-                context.getContentResolver(), uri, SliceSpecs.SUPPORTED_SPECS));
+                context.getContentResolver(), uri, unwrap(supportedSpecs)));
     }
 
 
@@ -415,16 +451,19 @@
      */
     @SuppressWarnings("NewApi")
     public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
+        // TODO: Hide this and only allow binding through SliceView.
         if (BuildCompat.isAtLeastP()) {
-            return callBindSlice(context, intent);
+            return callBindSlice(context, intent, Collections.<SliceSpec>emptyList());
         } else {
-            return SliceProviderCompat.bindSlice(context, intent);
+            return SliceProviderCompat.bindSlice(context, intent,
+                    Collections.<SliceSpec>emptyList());
         }
     }
 
     @TargetApi(28)
-    private static Slice callBindSlice(Context context, Intent intent) {
+    private static Slice callBindSlice(Context context, Intent intent,
+            List<SliceSpec> supportedSpecs) {
         return SliceConvert.wrap(android.app.slice.Slice.bindSlice(
-                context, intent, SliceSpecs.SUPPORTED_SPECS));
+                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
index edbc293..cf534d5 100644
--- a/slices/core/src/main/java/androidx/app/slice/SliceConvert.java
+++ b/slices/core/src/main/java/androidx/app/slice/SliceConvert.java
@@ -25,6 +25,10 @@
 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}
@@ -39,6 +43,7 @@
         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:
@@ -68,6 +73,20 @@
         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}
      */
@@ -75,6 +94,7 @@
         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:
@@ -103,4 +123,22 @@
         }
         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/SliceProvider.java b/slices/core/src/main/java/androidx/app/slice/SliceProvider.java
index a0c12f1..8ec2dbe 100644
--- a/slices/core/src/main/java/androidx/app/slice/SliceProvider.java
+++ b/slices/core/src/main/java/androidx/app/slice/SliceProvider.java
@@ -24,8 +24,11 @@
 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;
@@ -71,6 +74,8 @@
  */
 public abstract class SliceProvider extends ContentProviderWrapper {
 
+    private static List<SliceSpec> sSpecs;
+
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
         ContentProvider impl;
@@ -127,4 +132,20 @@
         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/compat/SliceProviderCompat.java b/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderCompat.java
index 9fcac1b..503ba0a 100644
--- a/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderCompat.java
+++ b/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderCompat.java
@@ -41,11 +41,13 @@
 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
@@ -60,6 +62,8 @@
     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());
@@ -130,8 +134,9 @@
                         Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                         "Slice binding requires the permission BIND_SLICE");
             }
+            List<SliceSpec> specs = getSpecs(extras);
 
-            Slice s = handleBindSlice(uri);
+            Slice s = handleBindSlice(uri, specs);
             Bundle b = new Bundle();
             b.putParcelable(EXTRA_SLICE, s.toBundle());
             return b;
@@ -144,7 +149,8 @@
             Uri uri = mSliceProvider.onMapIntentToUri(intent);
             Bundle b = new Bundle();
             if (uri != null) {
-                Slice s = handleBindSlice(uri);
+                List<SliceSpec> specs = getSpecs(extras);
+                Slice s = handleBindSlice(uri, specs);
                 b.putParcelable(EXTRA_SLICE, s.toBundle());
             } else {
                 b.putParcelable(EXTRA_SLICE, null);
@@ -154,16 +160,16 @@
         return super.call(method, arg, extras);
     }
 
-    private Slice handleBindSlice(final Uri sliceUri) {
+    private Slice handleBindSlice(final Uri sliceUri, final List<SliceSpec> specs) {
         if (Looper.myLooper() == Looper.getMainLooper()) {
-            return onBindSliceStrict(sliceUri);
+            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);
+                    output[0] = onBindSliceStrict(sliceUri, specs);
                     latch.countDown();
                 }
             });
@@ -176,13 +182,14 @@
         }
     }
 
-    private Slice onBindSliceStrict(Uri sliceUri) {
+    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);
             return mSliceProvider.onBindSlice(sliceUri);
         } finally {
             StrictMode.setThreadPolicy(oldPolicy);
@@ -190,9 +197,10 @@
     }
 
     /**
-     * Compat version of {@link Slice#bindSlice(Context, Uri)}.
+     * Compat version of {@link Slice#bindSlice}.
      */
-    public static Slice bindSlice(Context context, Uri uri) {
+    public static Slice bindSlice(Context context, Uri uri,
+            List<SliceSpec> supportedSpecs) {
         ContentProviderClient provider = context.getContentResolver()
                 .acquireContentProviderClient(uri);
         if (provider == null) {
@@ -201,6 +209,7 @@
         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;
@@ -219,16 +228,38 @@
         }
     }
 
+    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(Context, Intent)}.
+     * Compat version of {@link Slice#bindSlice}.
      */
-    public static Slice bindSlice(Context context, Intent intent) {
+    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);
+            return bindSlice(context, intentData, supportedSpecs);
         }
         // Otherwise ask the app
         List<ResolveInfo> providers =
@@ -246,6 +277,7 @@
         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;
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
index 3afed2b..438b964 100644
--- a/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderWrapper.java
+++ b/slices/core/src/main/java/androidx/app/slice/compat/SliceProviderWrapper.java
@@ -15,6 +15,8 @@
  */
 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;
@@ -49,6 +51,7 @@
 
     @Override
     public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedVersions) {
+        androidx.app.slice.SliceProvider.setSpecs(wrap(supportedVersions));
         return SliceConvert.unwrap(mSliceProvider.onBindSlice(sliceUri));
     }
 
diff --git a/slices/core/src/main/java/androidx/app/slice/core/SliceSpecs.java b/slices/core/src/main/java/androidx/app/slice/core/SliceSpecs.java
deleted file mode 100644
index a21633b..0000000
--- a/slices/core/src/main/java/androidx/app/slice/core/SliceSpecs.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 android.app.slice.SliceSpec;
-import android.support.annotation.RestrictTo;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class SliceSpecs {
-
-    // TODO: Fill these in.
-    public static List<SliceSpec> SUPPORTED_SPECS = Collections.emptyList();
-}
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
index 9b36ee1..8ef221a 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/SliceLiveData.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceLiveData.java
@@ -24,7 +24,11 @@
 import android.os.Handler;
 import android.support.annotation.NonNull;
 
+import java.util.Arrays;
+import java.util.List;
+
 import androidx.app.slice.Slice;
+import androidx.app.slice.SliceSpec;
 
 /**
  * Class with factory methods for creating LiveData that observes slices.
@@ -34,6 +38,8 @@
  */
 public final class SliceLiveData {
 
+    private static final List<SliceSpec> SUPPORTED_SPECS = Arrays.asList();
+
     /**
      * 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