Slice.java revision 9e9f857ec07b05e1a6885c9fc6a2bf25b813259b
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.slice;
18
19import static android.app.slice.Slice.HINT_ACTIONS;
20import static android.app.slice.Slice.HINT_HORIZONTAL;
21import static android.app.slice.Slice.HINT_LARGE;
22import static android.app.slice.Slice.HINT_LIST;
23import static android.app.slice.Slice.HINT_LIST_ITEM;
24import static android.app.slice.Slice.HINT_NO_TINT;
25import static android.app.slice.Slice.HINT_PARTIAL;
26import static android.app.slice.Slice.HINT_SEE_MORE;
27import static android.app.slice.Slice.HINT_SELECTED;
28import static android.app.slice.Slice.HINT_SHORTCUT;
29import static android.app.slice.Slice.HINT_SUMMARY;
30import static android.app.slice.Slice.HINT_TITLE;
31import static android.app.slice.SliceItem.FORMAT_ACTION;
32import static android.app.slice.SliceItem.FORMAT_IMAGE;
33import static android.app.slice.SliceItem.FORMAT_INT;
34import static android.app.slice.SliceItem.FORMAT_LONG;
35import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
36import static android.app.slice.SliceItem.FORMAT_SLICE;
37import static android.app.slice.SliceItem.FORMAT_TEXT;
38
39import static androidx.slice.SliceConvert.unwrap;
40import static androidx.slice.core.SliceHints.HINT_KEYWORDS;
41import static androidx.slice.core.SliceHints.HINT_LAST_UPDATED;
42import static androidx.slice.core.SliceHints.HINT_PERMISSION_REQUEST;
43import static androidx.slice.core.SliceHints.HINT_TTL;
44
45import android.app.PendingIntent;
46import android.app.RemoteInput;
47import android.app.slice.SliceManager;
48import android.content.Context;
49import android.net.Uri;
50import android.os.Bundle;
51import android.os.Parcelable;
52
53import androidx.annotation.NonNull;
54import androidx.annotation.Nullable;
55import androidx.annotation.RequiresApi;
56import androidx.annotation.RestrictTo;
57import androidx.annotation.RestrictTo.Scope;
58import androidx.annotation.StringDef;
59import androidx.core.graphics.drawable.IconCompat;
60import androidx.core.os.BuildCompat;
61import androidx.core.util.Consumer;
62import androidx.slice.compat.SliceProviderCompat;
63
64import java.util.ArrayList;
65import java.util.Arrays;
66import java.util.List;
67import java.util.Set;
68
69/**
70 * A slice is a piece of app content and actions that can be surfaced outside of the app.
71 *
72 * <p>They are constructed using {@link androidx.slice.builders.TemplateSliceBuilder}s
73 * in a tree structure that provides the OS some information about how the content should be
74 * displayed.
75 */
76public final class Slice {
77
78    private static final String HINTS = "hints";
79    private static final String ITEMS = "items";
80    private static final String URI = "uri";
81    private static final String SPEC_TYPE = "type";
82    private static final String SPEC_REVISION = "revision";
83    private final SliceSpec mSpec;
84
85    /**
86     * @hide
87     */
88    @RestrictTo(Scope.LIBRARY)
89    @StringDef({
90            HINT_TITLE,
91            HINT_LIST,
92            HINT_LIST_ITEM,
93            HINT_LARGE,
94            HINT_ACTIONS,
95            HINT_SELECTED,
96            HINT_HORIZONTAL,
97            HINT_NO_TINT,
98            HINT_PARTIAL,
99            HINT_SUMMARY,
100            HINT_SEE_MORE,
101            HINT_SHORTCUT,
102            HINT_KEYWORDS,
103            HINT_TTL,
104            HINT_LAST_UPDATED,
105            HINT_PERMISSION_REQUEST,
106    })
107    public @interface SliceHint{ }
108
109    private final SliceItem[] mItems;
110    private final @SliceHint String[] mHints;
111    private Uri mUri;
112
113    /**
114     * @hide
115     */
116    @RestrictTo(Scope.LIBRARY)
117    Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri,
118            SliceSpec spec) {
119        mHints = hints;
120        mItems = items.toArray(new SliceItem[items.size()]);
121        mUri = uri;
122        mSpec = spec;
123    }
124
125    /**
126     * @hide
127     */
128    @RestrictTo(Scope.LIBRARY)
129    public Slice(Bundle in) {
130        mHints = in.getStringArray(HINTS);
131        Parcelable[] items = in.getParcelableArray(ITEMS);
132        mItems = new SliceItem[items.length];
133        for (int i = 0; i < mItems.length; i++) {
134            if (items[i] instanceof Bundle) {
135                mItems[i] = new SliceItem((Bundle) items[i]);
136            }
137        }
138        mUri = in.getParcelable(URI);
139        mSpec = in.containsKey(SPEC_TYPE)
140                ? new SliceSpec(in.getString(SPEC_TYPE), in.getInt(SPEC_REVISION))
141                : null;
142    }
143
144    /**
145     * @hide
146     */
147    @RestrictTo(Scope.LIBRARY)
148    public Bundle toBundle() {
149        Bundle b = new Bundle();
150        b.putStringArray(HINTS, mHints);
151        Parcelable[] p = new Parcelable[mItems.length];
152        for (int i = 0; i < mItems.length; i++) {
153            p[i] = mItems[i].toBundle();
154        }
155        b.putParcelableArray(ITEMS, p);
156        b.putParcelable(URI, mUri);
157        if (mSpec != null) {
158            b.putString(SPEC_TYPE, mSpec.getType());
159            b.putInt(SPEC_REVISION, mSpec.getRevision());
160        }
161        return b;
162    }
163
164    /**
165     * @return The spec for this slice
166     * @hide
167     */
168    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
169    public @Nullable SliceSpec getSpec() {
170        return mSpec;
171    }
172
173    /**
174     * @return The Uri that this Slice represents.
175     */
176    public Uri getUri() {
177        return mUri;
178    }
179
180    /**
181     * @return All child {@link SliceItem}s that this Slice contains.
182     */
183    public List<SliceItem> getItems() {
184        return Arrays.asList(mItems);
185    }
186
187    /**
188     * @return All hints associated with this Slice.
189     */
190    public @SliceHint List<String> getHints() {
191        return Arrays.asList(mHints);
192    }
193
194    /**
195     * @hide
196     */
197    @RestrictTo(Scope.LIBRARY_GROUP)
198    public boolean hasHint(@SliceHint String hint) {
199        return ArrayUtils.contains(mHints, hint);
200    }
201
202    /**
203     * A Builder used to construct {@link Slice}s
204     * @hide
205     */
206    @RestrictTo(Scope.LIBRARY_GROUP)
207    public static class Builder {
208
209        private final Uri mUri;
210        private ArrayList<SliceItem> mItems = new ArrayList<>();
211        private @SliceHint ArrayList<String> mHints = new ArrayList<>();
212        private SliceSpec mSpec;
213
214        /**
215         * Create a builder which will construct a {@link Slice} for the Given Uri.
216         * @param uri Uri to tag for this slice.
217         */
218        public Builder(@NonNull Uri uri) {
219            mUri = uri;
220        }
221
222        /**
223         * Create a builder for a {@link Slice} that is a sub-slice of the slice
224         * being constructed by the provided builder.
225         * @param parent The builder constructing the parent slice
226         */
227        public Builder(@NonNull Slice.Builder parent) {
228            mUri = parent.mUri.buildUpon().appendPath("_gen")
229                    .appendPath(String.valueOf(mItems.size())).build();
230        }
231
232        /**
233         * Add the spec for this slice.
234         * @hide
235         */
236        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
237        public Builder setSpec(SliceSpec spec) {
238            mSpec = spec;
239            return this;
240        }
241
242        /**
243         * Add hints to the Slice being constructed
244         */
245        public Builder addHints(@SliceHint String... hints) {
246            mHints.addAll(Arrays.asList(hints));
247            return this;
248        }
249
250        /**
251         * Add hints to the Slice being constructed
252         */
253        public Builder addHints(@SliceHint List<String> hints) {
254            return addHints(hints.toArray(new String[hints.size()]));
255        }
256
257        /**
258         * Add a sub-slice to the slice being constructed
259         */
260        public Builder addSubSlice(@NonNull Slice slice) {
261            return addSubSlice(slice, null);
262        }
263
264        /**
265         * Add a sub-slice to the slice being constructed
266         * @param subType Optional template-specific type information
267         * @see {@link SliceItem#getSubType()}
268         */
269        public Builder addSubSlice(@NonNull Slice slice, String subType) {
270            mItems.add(new SliceItem(slice, FORMAT_SLICE, subType, slice.getHints().toArray(
271                    new String[slice.getHints().size()])));
272            return this;
273        }
274
275        /**
276         * Add an action to the slice being constructed
277         * @param subType Optional template-specific type information
278         * @see {@link SliceItem#getSubType()}
279         */
280        public Slice.Builder addAction(@NonNull PendingIntent action,
281                @NonNull Slice s, @Nullable String subType) {
282            @SliceHint String[] hints = s != null
283                    ? s.getHints().toArray(new String[s.getHints().size()]) : new String[0];
284            mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, hints));
285            return this;
286        }
287
288        /**
289         * Add an action to the slice being constructed
290         * @param subType Optional template-specific type information
291         * @see {@link SliceItem#getSubType()}
292         */
293        public Slice.Builder addAction(@NonNull Consumer<Uri> action,
294                @NonNull Slice s, @Nullable String subType) {
295            @SliceHint String[] hints = s != null
296                    ? s.getHints().toArray(new String[s.getHints().size()]) : new String[0];
297            mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, hints));
298            return this;
299        }
300
301        /**
302         * Add text to the slice being constructed
303         * @param subType Optional template-specific type information
304         * @see {@link SliceItem#getSubType()}
305         */
306        public Builder addText(CharSequence text, @Nullable String subType,
307                @SliceHint String... hints) {
308            mItems.add(new SliceItem(text, FORMAT_TEXT, subType, hints));
309            return this;
310        }
311
312        /**
313         * Add text to the slice being constructed
314         * @param subType Optional template-specific type information
315         * @see {@link SliceItem#getSubType()}
316         */
317        public Builder addText(CharSequence text, @Nullable String subType,
318                @SliceHint List<String> hints) {
319            return addText(text, subType, hints.toArray(new String[hints.size()]));
320        }
321
322        /**
323         * Add an image to the slice being constructed
324         * @param subType Optional template-specific type information
325         * @see {@link SliceItem#getSubType()}
326         */
327        public Builder addIcon(IconCompat icon, @Nullable String subType,
328                @SliceHint String... hints) {
329            mItems.add(new SliceItem(icon, FORMAT_IMAGE, subType, hints));
330            return this;
331        }
332
333        /**
334         * Add an image to the slice being constructed
335         * @param subType Optional template-specific type information
336         * @see {@link SliceItem#getSubType()}
337         */
338        public Builder addIcon(IconCompat icon, @Nullable String subType,
339                @SliceHint List<String> hints) {
340            return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
341        }
342
343        /**
344         * Add remote input to the slice being constructed
345         * @param subType Optional template-specific type information
346         * @see {@link SliceItem#getSubType()}
347         * @hide
348         */
349        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
350        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
351                @SliceHint List<String> hints) {
352            return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
353        }
354
355        /**
356         * Add remote input to the slice being constructed
357         * @param subType Optional template-specific type information
358         * @see {@link SliceItem#getSubType()}
359         * @hide
360         */
361        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
362        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
363                @SliceHint String... hints) {
364            mItems.add(new SliceItem(remoteInput, FORMAT_REMOTE_INPUT, subType, hints));
365            return this;
366        }
367
368        /**
369         * Add a int to the slice being constructed
370         * @param subType Optional template-specific type information
371         * @see {@link SliceItem#getSubType()}
372         */
373        public Builder addInt(int value, @Nullable String subType,
374                @SliceHint String... hints) {
375            mItems.add(new SliceItem(value, FORMAT_INT, subType, hints));
376            return this;
377        }
378
379        /**
380         * Add a int to the slice being constructed
381         * @param subType Optional template-specific type information
382         * @see {@link SliceItem#getSubType()}
383         */
384        public Builder addInt(int value, @Nullable String subType,
385                @SliceHint List<String> hints) {
386            return addInt(value, subType, hints.toArray(new String[hints.size()]));
387        }
388
389        /**
390         * Add a long to the slice being constructed
391         * @param subType Optional template-specific type information
392         * @see {@link SliceItem#getSubType()}
393         */
394        public Slice.Builder addLong(long time, @Nullable String subType,
395                @SliceHint String... hints) {
396            mItems.add(new SliceItem(time, FORMAT_LONG, subType, hints));
397            return this;
398        }
399
400        /**
401         * Add a long to the slice being constructed
402         * @param subType Optional template-specific type information
403         * @see {@link SliceItem#getSubType()}
404         */
405        public Slice.Builder addLong(long time, @Nullable String subType,
406                @SliceHint List<String> hints) {
407            return addLong(time, subType, hints.toArray(new String[hints.size()]));
408        }
409
410        /**
411         * Add a timestamp to the slice being constructed
412         * @param subType Optional template-specific type information
413         * @see {@link SliceItem#getSubType()}
414         * @deprecated TO BE REMOVED
415         */
416        @Deprecated
417        public Slice.Builder addTimestamp(long time, @Nullable String subType,
418                @SliceHint String... hints) {
419            mItems.add(new SliceItem(time, FORMAT_LONG, subType, hints));
420            return this;
421        }
422
423        /**
424         * Add a timestamp to the slice being constructed
425         * @param subType Optional template-specific type information
426         * @see {@link SliceItem#getSubType()}
427         */
428        public Slice.Builder addTimestamp(long time, @Nullable String subType,
429                @SliceHint List<String> hints) {
430            return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
431        }
432
433        /**
434         * Add a SliceItem to the slice being constructed.
435         * @hide
436         */
437        @RestrictTo(Scope.LIBRARY)
438        public Slice.Builder addItem(SliceItem item) {
439            mItems.add(item);
440            return this;
441        }
442
443        /**
444         * Construct the slice.
445         */
446        public Slice build() {
447            return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
448        }
449    }
450
451    /**
452     * @return A string representation of this slice.
453     */
454    @Override
455    public String toString() {
456        return toString("");
457    }
458
459    /**
460     * @return A string representation of this slice.
461     * @hide
462     */
463    @RestrictTo(Scope.LIBRARY)
464    public String toString(String indent) {
465        StringBuilder sb = new StringBuilder();
466        sb.append(indent);
467        sb.append("slice ");
468        addHints(sb, mHints);
469        sb.append("{\n");
470        String nextIndent = indent + "  ";
471        for (int i = 0; i < mItems.length; i++) {
472            SliceItem item = mItems[i];
473            sb.append(item.toString(nextIndent));
474        }
475        sb.append(indent);
476        sb.append("}");
477        return sb.toString();
478    }
479
480    /**
481     * @hide
482     */
483    @RestrictTo(Scope.LIBRARY)
484    public static void addHints(StringBuilder sb, String[] hints) {
485        if (hints.length == 0) return;
486
487        sb.append("(");
488        int end = hints.length - 1;
489        for (int i = 0; i < end; i++) {
490            sb.append(hints[i]);
491            sb.append(", ");
492        }
493        sb.append(hints[end]);
494        sb.append(") ");
495    }
496
497    /**
498     * Turns a slice Uri into slice content.
499     *
500     * @hide
501     * @param context Context to be used.
502     * @param uri The URI to a slice provider
503     * @return The Slice provided by the app or null if none is given.
504     * @see Slice
505     */
506    @RestrictTo(Scope.LIBRARY_GROUP)
507    @SuppressWarnings("NewApi") // Lint doesn't understand BuildCompat.
508    @Nullable
509    public static Slice bindSlice(Context context, @NonNull Uri uri,
510            Set<SliceSpec> supportedSpecs) {
511        if (BuildCompat.isAtLeastP()) {
512            return callBindSlice(context, uri, supportedSpecs);
513        } else {
514            return SliceProviderCompat.bindSlice(context, uri, supportedSpecs);
515        }
516    }
517
518    @RequiresApi(28)
519    private static Slice callBindSlice(Context context, Uri uri,
520            Set<SliceSpec> supportedSpecs) {
521        return SliceConvert.wrap(context.getSystemService(SliceManager.class)
522                .bindSlice(uri, unwrap(supportedSpecs)));
523    }
524}
525