diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 9ccc552f77f57..02e70f55f27e7 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -47,6 +47,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.slice.Slice; +import android.slice.SliceProvider; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -178,6 +180,8 @@ public abstract class ContentResolver { public static final Intent ACTION_SYNC_CONN_STATUS_CHANGED = new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED"); + /** @hide */ + public static final String SCHEME_SLICE = "slice"; public static final String SCHEME_CONTENT = "content"; public static final String SCHEME_ANDROID_RESOURCE = "android.resource"; public static final String SCHEME_FILE = "file"; @@ -1717,6 +1721,36 @@ public abstract class ContentResolver { } } + /** + * Turns a slice Uri into slice content. + * + * @param uri The URI to a slice provider + * @return The Slice provided by the app or null if none is given. + * @see Slice + * @hide + */ + public final @Nullable Slice bindSlice(@NonNull Uri uri) { + Preconditions.checkNotNull(uri, "uri"); + IContentProvider provider = acquireProvider(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + final Bundle res = provider.call(mPackageName, SliceProvider.METHOD_SLICE, null, + extras); + Bundle.setDefusable(res, true); + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + releaseProvider(provider); + } + } + /** * Returns the content provider for the given content URI. * @@ -1725,7 +1759,7 @@ public abstract class ContentResolver { * @hide */ public final IContentProvider acquireProvider(Uri uri) { - if (!SCHEME_CONTENT.equals(uri.getScheme())) { + if (!SCHEME_CONTENT.equals(uri.getScheme()) && !SCHEME_SLICE.equals(uri.getScheme())) { return null; } final String auth = uri.getAuthority(); diff --git a/core/java/android/slice/Slice.java b/core/java/android/slice/Slice.java new file mode 100644 index 0000000000000..bb810e634e4ff --- /dev/null +++ b/core/java/android/slice/Slice.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.slice; + +import static android.slice.SliceItem.TYPE_ACTION; +import static android.slice.SliceItem.TYPE_COLOR; +import static android.slice.SliceItem.TYPE_IMAGE; +import static android.slice.SliceItem.TYPE_REMOTE_INPUT; +import static android.slice.SliceItem.TYPE_REMOTE_VIEW; +import static android.slice.SliceItem.TYPE_SLICE; +import static android.slice.SliceItem.TYPE_TEXT; +import static android.slice.SliceItem.TYPE_TIMESTAMP; + +import android.annotation.NonNull; +import android.annotation.StringDef; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.widget.RemoteViews; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * A slice is a piece of app content and actions that can be surfaced outside of the app. + * + *

They are constructed using {@link Builder} in a tree structure + * that provides the OS some information about how the content should be displayed. + * @hide + */ +public final class Slice implements Parcelable { + + /** + * @hide + */ + @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED, + HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT}) + public @interface SliceHint{ } + + /** + * Hint that this content is a title of other content in the slice. + */ + public static final String HINT_TITLE = "title"; + /** + * Hint that all sub-items/sub-slices within this content should be considered + * to have {@link #HINT_LIST_ITEM}. + */ + public static final String HINT_LIST = "list"; + /** + * Hint that this item is part of a list and should be formatted as if is part + * of a list. + */ + public static final String HINT_LIST_ITEM = "list_item"; + /** + * Hint that this content is important and should be larger when displayed if + * possible. + */ + public static final String HINT_LARGE = "large"; + /** + * Hint that this slice contains a number of actions that can be grouped together + * in a sort of controls area of the UI. + */ + public static final String HINT_ACTIONS = "actions"; + /** + * Hint indicating that this item (and its sub-items) are the current selection. + */ + public static final String HINT_SELECTED = "selected"; + /** + * Hint to indicate that this is a message as part of a communication + * sequence in this slice. + */ + public static final String HINT_MESSAGE = "message"; + /** + * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}. + */ + public static final String HINT_SOURCE = "source"; + /** + * Hint that list items within this slice or subslice would appear better + * if organized horizontally. + */ + public static final String HINT_HORIZONTAL = "horizontal"; + /** + * Hint to indicate that this content should not be tinted. + */ + public static final String HINT_NO_TINT = "no_tint"; + + // These two are coming over from prototyping, but we probably don't want in + // public API, at least not right now. + /** + * @hide + */ + public static final String HINT_ALT = "alt"; + /** + * @hide + */ + public static final String HINT_PARTIAL = "partial"; + + private final SliceItem[] mItems; + private final @SliceHint String[] mHints; + private Uri mUri; + + /** + * @hide + */ + public Slice(ArrayList items, @SliceHint String[] hints, Uri uri) { + mHints = hints; + mItems = items.toArray(new SliceItem[items.size()]); + mUri = uri; + } + + protected Slice(Parcel in) { + mHints = in.readStringArray(); + int n = in.readInt(); + mItems = new SliceItem[n]; + for (int i = 0; i < n; i++) { + mItems[i] = SliceItem.CREATOR.createFromParcel(in); + } + mUri = Uri.CREATOR.createFromParcel(in); + } + + /** + * @return The Uri that this slice represents. + */ + public Uri getUri() { + return mUri; + } + + /** + * @return All child {@link SliceItem}s that this Slice contains. + */ + public SliceItem[] getItems() { + return mItems; + } + + /** + * @return All hints associated with this Slice. + */ + public @SliceHint String[] getHints() { + return mHints; + } + + /** + * @hide + */ + public SliceItem getPrimaryIcon() { + for (SliceItem item : getItems()) { + if (item.getType() == TYPE_IMAGE) { + return item; + } + if (!(item.getType() == TYPE_SLICE && item.hasHint(Slice.HINT_LIST)) + && !item.hasHint(Slice.HINT_ACTIONS) + && !item.hasHint(Slice.HINT_LIST_ITEM) + && (item.getType() != TYPE_ACTION)) { + SliceItem icon = SliceQuery.find(item, TYPE_IMAGE); + if (icon != null) return icon; + } + } + return null; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStringArray(mHints); + dest.writeInt(mItems.length); + for (int i = 0; i < mItems.length; i++) { + mItems[i].writeToParcel(dest, flags); + } + mUri.writeToParcel(dest, 0); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * A Builder used to construct {@link Slice}s + */ + public static class Builder { + + private final Uri mUri; + private ArrayList mItems = new ArrayList<>(); + private @SliceHint ArrayList mHints = new ArrayList<>(); + + /** + * Create a builder which will construct a {@link Slice} for the Given Uri. + * @param uri Uri to tag for this slice. + */ + public Builder(@NonNull Uri uri) { + mUri = uri; + } + + /** + * Create a builder for a {@link Slice} that is a sub-slice of the slice + * being constructed by the provided builder. + * @param parent The builder constructing the parent slice + */ + public Builder(@NonNull Slice.Builder parent) { + mUri = parent.mUri.buildUpon().appendPath("_gen") + .appendPath(String.valueOf(mItems.size())).build(); + } + + /** + * Add hints to the Slice being constructed + */ + public Builder addHints(@SliceHint String... hints) { + mHints.addAll(Arrays.asList(hints)); + return this; + } + + /** + * Add a sub-slice to the slice being constructed + */ + public Builder addSubSlice(@NonNull Slice slice) { + mItems.add(new SliceItem(slice, TYPE_SLICE, slice.getHints())); + return this; + } + + /** + * Add an action to the slice being constructed + */ + public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) { + mItems.add(new SliceItem(action, s, TYPE_ACTION, new String[0])); + return this; + } + + /** + * Add text to the slice being constructed + */ + public Builder addText(CharSequence text, @SliceHint String... hints) { + mItems.add(new SliceItem(text, TYPE_TEXT, hints)); + return this; + } + + /** + * Add an image to the slice being constructed + */ + public Builder addIcon(Icon icon, @SliceHint String... hints) { + mItems.add(new SliceItem(icon, TYPE_IMAGE, hints)); + return this; + } + + /** + * @hide This isn't final + */ + public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) { + mItems.add(new SliceItem(remoteView, TYPE_REMOTE_VIEW, hints)); + return this; + } + + /** + * Add remote input to the slice being constructed + */ + public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) { + mItems.add(new SliceItem(remoteInput, TYPE_REMOTE_INPUT, hints)); + return this; + } + + /** + * Add a color to the slice being constructed + */ + public Builder addColor(int color, @SliceHint String... hints) { + mItems.add(new SliceItem(color, TYPE_COLOR, hints)); + return this; + } + + /** + * Add a timestamp to the slice being constructed + */ + public Slice.Builder addTimestamp(long time, @SliceHint String... hints) { + mItems.add(new SliceItem(time, TYPE_TIMESTAMP, hints)); + return this; + } + + /** + * Construct the slice. + */ + public Slice build() { + return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri); + } + } + + public static final Creator CREATOR = new Creator() { + @Override + public Slice createFromParcel(Parcel in) { + return new Slice(in); + } + + @Override + public Slice[] newArray(int size) { + return new Slice[size]; + } + }; +} diff --git a/core/java/android/slice/SliceItem.java b/core/java/android/slice/SliceItem.java new file mode 100644 index 0000000000000..16f7dc669f328 --- /dev/null +++ b/core/java/android/slice/SliceItem.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.slice; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; +import android.slice.Slice.SliceHint; +import android.text.TextUtils; +import android.util.Pair; +import android.widget.RemoteViews; + +import com.android.internal.util.ArrayUtils; + + +/** + * A SliceItem is a single unit in the tree structure of a {@link Slice}. + * + * A SliceItem a piece of content and some hints about what that content + * means or how it should be displayed. The types of content can be: + *

  • {@link #TYPE_SLICE}
  • + *
  • {@link #TYPE_TEXT}
  • + *
  • {@link #TYPE_IMAGE}
  • + *
  • {@link #TYPE_ACTION}
  • + *
  • {@link #TYPE_COLOR}
  • + *
  • {@link #TYPE_TIMESTAMP}
  • + *
  • {@link #TYPE_REMOTE_INPUT}
  • + * + * The hints that a {@link SliceItem} are a set of strings which annotate + * the content. The hints that are guaranteed to be understood by the system + * are defined on {@link Slice}. + * @hide + */ +public final class SliceItem implements Parcelable { + + /** + * @hide + */ + @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR, + TYPE_TIMESTAMP, TYPE_REMOTE_INPUT}) + public @interface SliceType {} + + /** + * A {@link SliceItem} that contains a {@link Slice} + */ + public static final int TYPE_SLICE = 1; + /** + * A {@link SliceItem} that contains a {@link CharSequence} + */ + public static final int TYPE_TEXT = 2; + /** + * A {@link SliceItem} that contains an {@link Icon} + */ + public static final int TYPE_IMAGE = 3; + /** + * A {@link SliceItem} that contains a {@link PendingIntent} + * + * Note: Actions contain 2 pieces of data, In addition to the pending intent, the + * item contains a {@link Slice} that the action applies to. + */ + public static final int TYPE_ACTION = 4; + /** + * @hide This isn't final + */ + public static final int TYPE_REMOTE_VIEW = 5; + /** + * A {@link SliceItem} that contains a Color int. + */ + public static final int TYPE_COLOR = 6; + /** + * A {@link SliceItem} that contains a timestamp. + */ + public static final int TYPE_TIMESTAMP = 8; + /** + * A {@link SliceItem} that contains a {@link RemoteInput}. + */ + public static final int TYPE_REMOTE_INPUT = 9; + + /** + * @hide + */ + protected @SliceHint String[] mHints; + private final int mType; + private final Object mObj; + + /** + * @hide + */ + public SliceItem(Object obj, @SliceType int type, @SliceHint String[] hints) { + mHints = hints; + mType = type; + mObj = obj; + } + + /** + * @hide + */ + public SliceItem(PendingIntent intent, Slice slice, int type, @SliceHint String[] hints) { + this(new Pair<>(intent, slice), type, hints); + } + + /** + * Gets all hints associated with this SliceItem. + * @return Array of hints. + */ + public @NonNull @SliceHint String[] getHints() { + return mHints; + } + + /** + * @hide + */ + public void addHint(@SliceHint String hint) { + mHints = ArrayUtils.appendElement(String.class, mHints, hint); + } + + public @SliceType int getType() { + return mType; + } + + /** + * @return The text held by this {@link #TYPE_TEXT} SliceItem + */ + public CharSequence getText() { + return (CharSequence) mObj; + } + + /** + * @return The icon held by this {@link #TYPE_IMAGE} SliceItem + */ + public Icon getIcon() { + return (Icon) mObj; + } + + /** + * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem + */ + public PendingIntent getAction() { + return ((Pair) mObj).first; + } + + /** + * @hide This isn't final + */ + public RemoteViews getRemoteView() { + return (RemoteViews) mObj; + } + + /** + * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem + */ + public RemoteInput getRemoteInput() { + return (RemoteInput) mObj; + } + + /** + * @return The color held by this {@link #TYPE_COLOR} SliceItem + */ + public int getColor() { + return (Integer) mObj; + } + + /** + * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem + */ + public Slice getSlice() { + if (getType() == TYPE_ACTION) { + return ((Pair) mObj).second; + } + return (Slice) mObj; + } + + /** + * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem + */ + public long getTimestamp() { + return (Long) mObj; + } + + /** + * @param hint The hint to check for + * @return true if this item contains the given hint + */ + public boolean hasHint(@SliceHint String hint) { + return ArrayUtils.contains(mHints, hint); + } + + /** + * @hide + */ + public SliceItem(Parcel in) { + mHints = in.readStringArray(); + mType = in.readInt(); + mObj = readObj(mType, in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStringArray(mHints); + dest.writeInt(mType); + writeObj(dest, flags, mObj, mType); + } + + /** + * @hide + */ + public boolean hasHints(@SliceHint String[] hints) { + if (hints == null) return true; + for (String hint : hints) { + if (!ArrayUtils.contains(mHints, hint)) { + return false; + } + } + return true; + } + + /** + * @hide + */ + public boolean hasAnyHints(@SliceHint String[] hints) { + if (hints == null) return true; + for (String hint : hints) { + if (ArrayUtils.contains(mHints, hint)) { + return true; + } + } + return false; + } + + private void writeObj(Parcel dest, int flags, Object obj, int type) { + switch (type) { + case TYPE_SLICE: + case TYPE_REMOTE_VIEW: + case TYPE_IMAGE: + case TYPE_REMOTE_INPUT: + ((Parcelable) obj).writeToParcel(dest, flags); + break; + case TYPE_ACTION: + ((Pair) obj).first.writeToParcel(dest, flags); + ((Pair) obj).second.writeToParcel(dest, flags); + break; + case TYPE_TEXT: + TextUtils.writeToParcel((CharSequence) mObj, dest, flags); + break; + case TYPE_COLOR: + dest.writeInt((Integer) mObj); + break; + case TYPE_TIMESTAMP: + dest.writeLong((Long) mObj); + break; + } + } + + private static Object readObj(int type, Parcel in) { + switch (type) { + case TYPE_SLICE: + return Slice.CREATOR.createFromParcel(in); + case TYPE_TEXT: + return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + case TYPE_IMAGE: + return Icon.CREATOR.createFromParcel(in); + case TYPE_ACTION: + return new Pair( + PendingIntent.CREATOR.createFromParcel(in), + Slice.CREATOR.createFromParcel(in)); + case TYPE_REMOTE_VIEW: + return RemoteViews.CREATOR.createFromParcel(in); + case TYPE_COLOR: + return in.readInt(); + case TYPE_TIMESTAMP: + return in.readLong(); + case TYPE_REMOTE_INPUT: + return RemoteInput.CREATOR.createFromParcel(in); + } + throw new RuntimeException("Unsupported type " + type); + } + + public static final Creator CREATOR = new Creator() { + @Override + public SliceItem createFromParcel(Parcel in) { + return new SliceItem(in); + } + + @Override + public SliceItem[] newArray(int size) { + return new SliceItem[size]; + } + }; +} diff --git a/core/java/android/slice/SliceProvider.java b/core/java/android/slice/SliceProvider.java new file mode 100644 index 0000000000000..4e21371b26b5a --- /dev/null +++ b/core/java/android/slice/SliceProvider.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.slice; + +import android.Manifest.permission; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import java.util.concurrent.CountDownLatch; + +/** + * A SliceProvider allows app to provide content to be displayed in system + * spaces. This content is templated and can contain actions, and the behavior + * of how it is surfaced is specific to the system surface. + * + *

    Slices are not currently live content. They are bound once and shown to the + * user. If the content changes due to a callback from user interaction, then + * {@link ContentResolver#notifyChange(Uri, ContentObserver)} + * should be used to notify the system.

    + * + *

    The provider needs to be declared in the manifest to provide the authority + * for the app. The authority for most slices is expected to match the package + * of the application.

    + *
    + * {@literal
    + * }
    + * 
    + * + * @see Slice + * @hide + */ +public abstract class SliceProvider extends ContentProvider { + + private static final String TAG = "SliceProvider"; + /** + * @hide + */ + public static final String EXTRA_BIND_URI = "slice_uri"; + /** + * @hide + */ + public static final String METHOD_SLICE = "bind_slice"; + /** + * @hide + */ + public static final String EXTRA_SLICE = "slice"; + + private static final boolean DEBUG = false; + + /** + * Implemented to create a slice. Will be called on the main thread. + * @see {@link Slice}. + */ + public abstract Slice onBindSlice(Uri sliceUri); + + @Override + public final int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + if (DEBUG) Log.d(TAG, "update " + uri); + return 0; + } + + @Override + public final int delete(Uri uri, String selection, String[] selectionArgs) { + if (DEBUG) Log.d(TAG, "delete " + uri); + return 0; + } + + @Override + public final Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + if (DEBUG) Log.d(TAG, "query " + uri); + return null; + } + + @Override + public final Cursor query(Uri uri, String[] projection, String selection, String[] + selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { + if (DEBUG) Log.d(TAG, "query " + uri); + return null; + } + + @Override + public final Cursor query(Uri uri, String[] projection, Bundle queryArgs, + CancellationSignal cancellationSignal) { + if (DEBUG) Log.d(TAG, "query " + uri); + return null; + } + + @Override + public final Uri insert(Uri uri, ContentValues values) { + if (DEBUG) Log.d(TAG, "insert " + uri); + return null; + } + + @Override + public final String getType(Uri uri) { + if (DEBUG) Log.d(TAG, "getType " + uri); + return null; + } + + @Override + public final Bundle call(String method, String arg, Bundle extras) { + if (method.equals(METHOD_SLICE)) { + getContext().enforceCallingPermission(permission.BIND_SLICE, + "Slice binding requires the permission BIND_SLICE"); + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + + Slice s = handleBindSlice(uri); + Bundle b = new Bundle(); + b.putParcelable(EXTRA_SLICE, s); + return b; + } + return super.call(method, arg, extras); + } + + private Slice handleBindSlice(Uri sliceUri) { + Slice[] output = new Slice[1]; + CountDownLatch latch = new CountDownLatch(1); + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(() -> { + output[0] = onBindSlice(sliceUri); + latch.countDown(); + }); + try { + latch.await(); + return output[0]; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/java/android/slice/SliceQuery.java b/core/java/android/slice/SliceQuery.java new file mode 100644 index 0000000000000..edac0ccaf1dda --- /dev/null +++ b/core/java/android/slice/SliceQuery.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.slice; + +import static android.slice.SliceItem.TYPE_ACTION; +import static android.slice.SliceItem.TYPE_SLICE; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * A bunch of utilities for searching the contents of a slice. + * @hide + */ +public class SliceQuery { + private static final String TAG = "SliceQuery"; + + /** + * @hide + */ + public static SliceItem findNotContaining(SliceItem container, List list) { + SliceItem ret = null; + while (ret == null && list.size() != 0) { + SliceItem remove = list.remove(0); + if (!contains(container, remove)) { + ret = remove; + } + } + return ret; + } + + /** + * @hide + */ + private static boolean contains(SliceItem container, SliceItem item) { + if (container == null || item == null) return false; + return stream(container).filter(s -> (s == item)).findAny().isPresent(); + } + + /** + * @hide + */ + public static List findAll(SliceItem s, int type, String hints, String nonHints) { + return findAll(s, type, new String[]{ hints }, new String[]{ nonHints }); + } + + /** + * @hide + */ + public static List findAll(SliceItem s, int type, String[] hints, + String[] nonHints) { + return stream(s).filter(item -> (type == -1 || item.getType() == type) + && (item.hasHints(hints) && !item.hasAnyHints(nonHints))) + .collect(Collectors.toList()); + } + + /** + * @hide + */ + public static SliceItem find(Slice s, int type, String hints, String nonHints) { + return find(s, type, new String[]{ hints }, new String[]{ nonHints }); + } + + /** + * @hide + */ + public static SliceItem find(SliceItem s, int type) { + return find(s, type, (String[]) null, null); + } + + /** + * @hide + */ + public static SliceItem find(SliceItem s, int type, String hints, String nonHints) { + return find(s, type, new String[]{ hints }, new String[]{ nonHints }); + } + + /** + * @hide + */ + public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) { + return find(new SliceItem(s, TYPE_SLICE, s.getHints()), type, hints, nonHints); + } + + /** + * @hide + */ + public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) { + return stream(s).filter(item -> (item.getType() == type || type == -1) + && (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null); + } + + /** + * @hide + */ + public static Stream stream(SliceItem slice) { + Queue items = new LinkedList(); + items.add(slice); + Iterator iterator = new Iterator() { + @Override + public boolean hasNext() { + return items.size() != 0; + } + + @Override + public SliceItem next() { + SliceItem item = items.poll(); + if (item.getType() == TYPE_SLICE || item.getType() == TYPE_ACTION) { + items.addAll(Arrays.asList(item.getSlice().getItems())); + } + return item; + } + }; + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a9fda8ed4cd2c..6a9afbcae1425 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3106,6 +3106,13 @@ + + +