From 2af1998e59caa897ef4400b333febba2e5b101c0 Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Tue, 7 Nov 2017 19:38:27 -0500 Subject: [PATCH] Add SliceSpec to Slice API SliceSpec will be used to handshake versions between Slice renderers and Slice providers. The renderer will pass in info about supported specs, and expect that the returned slice will be compatible. Test: manual Bug: 68378561 Change-Id: Id094ec3b6f682a3d84a2b992e1a8274212778970 --- api/current.txt | 19 ++- api/system-current.txt | 21 +++- api/test-current.txt | 19 ++- core/java/android/app/slice/Slice.java | 49 ++++++-- .../java/android/app/slice/SliceProvider.java | 38 ++++-- core/java/android/app/slice/SliceSpec.java | 117 ++++++++++++++++++ .../android/keyguard/KeyguardSliceView.java | 8 +- 7 files changed, 242 insertions(+), 29 deletions(-) create mode 100644 core/java/android/app/slice/SliceSpec.java diff --git a/api/current.txt b/api/current.txt index c6d5430dce168..7e4ee1a514064 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6973,11 +6973,12 @@ package android.app.slice { public final class Slice implements android.os.Parcelable { ctor protected Slice(android.os.Parcel); - method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri); - method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent); + method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List); + method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List); method public int describeContents(); method public java.util.List getHints(); method public java.util.List getItems(); + method public android.app.slice.SliceSpec getSpec(); method public android.net.Uri getUri(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -7012,6 +7013,7 @@ package android.app.slice { method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...); method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List); method public android.app.slice.Slice build(); + method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec); } public final class SliceItem implements android.os.Parcelable { @@ -7042,7 +7044,8 @@ package android.app.slice { method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public final java.lang.String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); - method public abstract android.app.slice.Slice onBindSlice(android.net.Uri); + method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List); + method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri); method public android.net.Uri onMapIntentToUri(android.content.Intent); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); @@ -7051,6 +7054,16 @@ package android.app.slice { field public static final java.lang.String SLICE_TYPE = "vnd.android.slice"; } + public final class SliceSpec implements android.os.Parcelable { + ctor public SliceSpec(java.lang.String, int); + method public boolean canRender(android.app.slice.SliceSpec); + method public int describeContents(); + method public int getRevision(); + method public java.lang.String getType(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + } package android.app.usage { diff --git a/api/system-current.txt b/api/system-current.txt index 94e378680acb0..a6e1eabbd6386 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -7417,11 +7417,12 @@ package android.app.slice { public final class Slice implements android.os.Parcelable { ctor protected Slice(android.os.Parcel); - method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri); - method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent); + method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List); + method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List); method public int describeContents(); method public java.util.List getHints(); method public java.util.List getItems(); + method public android.app.slice.SliceSpec getSpec(); method public android.net.Uri getUri(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -7456,6 +7457,7 @@ package android.app.slice { method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...); method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List); method public android.app.slice.Slice build(); + method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec); } public final class SliceItem implements android.os.Parcelable { @@ -7486,7 +7488,8 @@ package android.app.slice { method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public final java.lang.String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); - method public abstract android.app.slice.Slice onBindSlice(android.net.Uri); + method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List); + method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri); method public android.net.Uri onMapIntentToUri(android.content.Intent); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); @@ -7495,6 +7498,16 @@ package android.app.slice { field public static final java.lang.String SLICE_TYPE = "vnd.android.slice"; } + public final class SliceSpec implements android.os.Parcelable { + ctor public SliceSpec(java.lang.String, int); + method public boolean canRender(android.app.slice.SliceSpec); + method public int describeContents(); + method public int getRevision(); + method public java.lang.String getType(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + } package android.app.usage { @@ -48173,8 +48186,8 @@ package android.util { } public final class StatsManager { - method public byte[] getData(java.lang.String); method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String); + method public byte[] getData(java.lang.String); method public boolean removeConfiguration(java.lang.String); } diff --git a/api/test-current.txt b/api/test-current.txt index aebf380349bb5..f9dd65c6ad14a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -7047,11 +7047,12 @@ package android.app.slice { public final class Slice implements android.os.Parcelable { ctor protected Slice(android.os.Parcel); - method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri); - method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent); + method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List); + method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List); method public int describeContents(); method public java.util.List getHints(); method public java.util.List getItems(); + method public android.app.slice.SliceSpec getSpec(); method public android.net.Uri getUri(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -7086,6 +7087,7 @@ package android.app.slice { method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...); method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List); method public android.app.slice.Slice build(); + method public android.app.slice.Slice.Builder setSpec(android.app.slice.SliceSpec); } public final class SliceItem implements android.os.Parcelable { @@ -7116,7 +7118,8 @@ package android.app.slice { method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public final java.lang.String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); - method public abstract android.app.slice.Slice onBindSlice(android.net.Uri); + method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List); + method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri); method public android.net.Uri onMapIntentToUri(android.content.Intent); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); @@ -7125,6 +7128,16 @@ package android.app.slice { field public static final java.lang.String SLICE_TYPE = "vnd.android.slice"; } + public final class SliceSpec implements android.os.Parcelable { + ctor public SliceSpec(java.lang.String, int); + method public boolean canRender(android.app.slice.SliceSpec); + method public int describeContents(); + method public int getRevision(); + method public java.lang.String getType(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + } package android.app.usage { diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index a4aeb67ab4914..58fb260814e59 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -143,12 +143,14 @@ public final class Slice implements Parcelable { private final SliceItem[] mItems; private final @SliceHint String[] mHints; + private SliceSpec mSpec; private Uri mUri; - Slice(ArrayList items, @SliceHint String[] hints, Uri uri) { + Slice(ArrayList items, @SliceHint String[] hints, Uri uri, SliceSpec spec) { mHints = hints; mItems = items.toArray(new SliceItem[items.size()]); mUri = uri; + mSpec = spec; } protected Slice(Parcel in) { @@ -159,6 +161,14 @@ public final class Slice implements Parcelable { mItems[i] = SliceItem.CREATOR.createFromParcel(in); } mUri = Uri.CREATOR.createFromParcel(in); + mSpec = in.readTypedObject(SliceSpec.CREATOR); + } + + /** + * @return The spec for this slice + */ + public @Nullable SliceSpec getSpec() { + return mSpec; } /** @@ -190,6 +200,7 @@ public final class Slice implements Parcelable { mItems[i].writeToParcel(dest, flags); } mUri.writeToParcel(dest, 0); + dest.writeTypedObject(mSpec, flags); } @Override @@ -212,6 +223,7 @@ public final class Slice implements Parcelable { private final Uri mUri; private ArrayList mItems = new ArrayList<>(); private @SliceHint ArrayList mHints = new ArrayList<>(); + private SliceSpec mSpec; /** * Create a builder which will construct a {@link Slice} for the Given Uri. @@ -246,12 +258,22 @@ public final class Slice implements Parcelable { return addHints(hints.toArray(new String[hints.size()])); } + /** + * Add the spec for this slice. + */ + public Builder setSpec(SliceSpec spec) { + mSpec = spec; + return this; + } + /** * Add a sub-slice to the slice being constructed */ public Builder addSubSlice(@NonNull Slice slice) { - mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray( - new String[slice.getHints().size()]))); + List hints = slice.getHints(); + slice.mSpec = null; + mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, hints.toArray( + new String[hints.size()]))); return this; } @@ -259,7 +281,10 @@ public final class Slice implements Parcelable { * Add an action to the slice being constructed */ public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) { - mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, new String[0])); + List hints = s.getHints(); + s.mSpec = null; + mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, hints.toArray( + new String[hints.size()]))); return this; } @@ -351,7 +376,7 @@ public final class Slice implements Parcelable { * 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); } } @@ -399,10 +424,12 @@ public final class Slice implements Parcelable { * * @param resolver ContentResolver to be used. * @param uri The URI to a slice provider + * @param supportedSpecs List of supported specs. * @return The Slice provided by the app or null if none is given. * @see Slice */ - public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri) { + public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri, + List supportedSpecs) { Preconditions.checkNotNull(uri, "uri"); IContentProvider provider = resolver.acquireProvider(uri); if (provider == null) { @@ -411,6 +438,8 @@ public final class Slice implements Parcelable { try { Bundle extras = new Bundle(); extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, + new ArrayList<>(supportedSpecs)); final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE, null, extras); Bundle.setDefusable(res, true); @@ -434,12 +463,14 @@ public final class Slice implements Parcelable { * * @param context The context to use. * @param intent The intent associated with a slice. + * @param supportedSpecs List of supported specs. * @return The Slice provided by the app or null if none is given. * @see Slice * @see SliceProvider#onMapIntentToUri(Intent) * @see Intent */ - public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) { + public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent, + List supportedSpecs) { Preconditions.checkNotNull(intent, "intent"); Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null, "Slice intent must be explicit " + intent); @@ -448,7 +479,7 @@ public final class Slice implements Parcelable { // Check if the intent has data for the slice uri on it and use that final Uri intentData = intent.getData(); if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { - return bindSlice(resolver, intentData); + return bindSlice(resolver, intentData, supportedSpecs); } // Otherwise ask the app List providers = @@ -466,6 +497,8 @@ public final class Slice implements Parcelable { try { Bundle extras = new Bundle(); extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); + extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, + new ArrayList<>(supportedSpecs)); final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_MAP_INTENT, null, extras); if (res == null) { diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index 4f9c168b8b45c..ac5365c35f497 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -36,6 +36,7 @@ import android.os.StrictMode.ThreadPolicy; import android.os.UserHandle; import android.util.Log; +import java.util.List; import java.util.concurrent.CountDownLatch; /** @@ -89,6 +90,10 @@ public abstract class SliceProvider extends ContentProvider { * @hide */ public static final String EXTRA_BIND_URI = "slice_uri"; + /** + * @hide + */ + public static final String EXTRA_SUPPORTED_SPECS = "supported_specs"; /** * @hide */ @@ -117,12 +122,25 @@ public abstract class SliceProvider extends ContentProvider { * 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. *

+ * The slice returned should have a spec that is compatible with one of + * the supported specs. * + * @param sliceUri Uri to bind. + * @param supportedSpecs List of supported specs. * @see {@link Slice}. * @see {@link Slice#HINT_PARTIAL} */ - // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)). - public abstract Slice onBindSlice(Uri sliceUri); + public Slice onBindSlice(Uri sliceUri, List supportedSpecs) { + return onBindSlice(sliceUri); + } + + /** + * @deprecated migrating to {@link #onBindSlice(Uri, List)} + */ + @Deprecated + public Slice onBindSlice(Uri sliceUri) { + return null; + } /** * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider. @@ -193,8 +211,9 @@ public abstract class SliceProvider extends ContentProvider { Intent.FLAG_GRANT_WRITE_URI_PERMISSION, "Slice binding requires the permission BIND_SLICE"); } + List supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); - Slice s = handleBindSlice(uri); + Slice s = handleBindSlice(uri, supportedSpecs); Bundle b = new Bundle(); b.putParcelable(EXTRA_SLICE, s); return b; @@ -203,9 +222,10 @@ public abstract class SliceProvider extends ContentProvider { "Slice binding requires the permission BIND_SLICE"); Intent intent = extras.getParcelable(EXTRA_INTENT); Uri uri = onMapIntentToUri(intent); + List supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); Bundle b = new Bundle(); if (uri != null) { - Slice s = handleBindSlice(uri); + Slice s = handleBindSlice(uri, supportedSpecs); b.putParcelable(EXTRA_SLICE, s); } else { b.putParcelable(EXTRA_SLICE, null); @@ -215,14 +235,14 @@ public abstract class SliceProvider extends ContentProvider { return super.call(method, arg, extras); } - private Slice handleBindSlice(Uri sliceUri) { + private Slice handleBindSlice(Uri sliceUri, List supportedSpecs) { if (Looper.myLooper() == Looper.getMainLooper()) { - return onBindSliceStrict(sliceUri); + return onBindSliceStrict(sliceUri, supportedSpecs); } else { CountDownLatch latch = new CountDownLatch(1); Slice[] output = new Slice[1]; Handler.getMain().post(() -> { - output[0] = onBindSliceStrict(sliceUri); + output[0] = onBindSliceStrict(sliceUri, supportedSpecs); latch.countDown(); }); try { @@ -234,14 +254,14 @@ public abstract class SliceProvider extends ContentProvider { } } - private Slice onBindSliceStrict(Uri sliceUri) { + private Slice onBindSliceStrict(Uri sliceUri, List supportedSpecs) { ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); try { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build()); - return onBindSlice(sliceUri); + return onBindSlice(sliceUri, supportedSpecs); } finally { StrictMode.setThreadPolicy(oldPolicy); } diff --git a/core/java/android/app/slice/SliceSpec.java b/core/java/android/app/slice/SliceSpec.java new file mode 100644 index 0000000000000..433b67e9aacbe --- /dev/null +++ b/core/java/android/app/slice/SliceSpec.java @@ -0,0 +1,117 @@ +/* + * 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.app.slice; + +import android.annotation.NonNull; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Class describing the structure of the data contained within a slice. + *

+ * 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. + *

+ * 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}. + * + * @see Slice + * @see SliceProvider#onBindSlice(Uri) + */ +public final class SliceSpec implements Parcelable { + + private final String mType; + private final int mRevision; + + public SliceSpec(@NonNull String type, int revision) { + mType = type; + mRevision = revision; + } + + /** + * @hide + */ + public SliceSpec(Parcel source) { + mType = source.readString(); + mRevision = source.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mType); + dest.writeInt(mRevision); + } + + /** + * 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. + *

+ * 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; + } + + public static final Creator CREATOR = new Creator() { + @Override + public SliceSpec createFromParcel(Parcel source) { + return new SliceSpec(source); + } + + @Override + public SliceSpec[] newArray(int size) { + return new SliceSpec[size]; + } + }; +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index c18f9b61c0447..104a77def44ef 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -33,6 +33,8 @@ import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardSliceProvider; +import java.util.Collections; + /** * View visible under the clock on the lock screen and AoD. */ @@ -75,7 +77,8 @@ public class KeyguardSliceView extends LinearLayout { super.onAttachedToWindow(); // Set initial content - showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri)); + showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri, + Collections.emptyList())); // Make sure we always have the most current slice getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri, @@ -154,7 +157,8 @@ public class KeyguardSliceView extends LinearLayout { @Override public void onChange(boolean selfChange, Uri uri) { - showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri)); + showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri, + Collections.emptyList())); } } }