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