Slice.java revision 6356d81d685e0e35f360f8e2aee1991c06274a0b
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_REMOTE_INPUT;
35import static android.app.slice.SliceItem.FORMAT_SLICE;
36import static android.app.slice.SliceItem.FORMAT_TEXT;
37import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
38
39import static androidx.slice.SliceConvert.unwrap;
40import static androidx.slice.core.SliceHints.HINT_KEY_WORDS;
41
42import android.app.PendingIntent;
43import android.app.RemoteInput;
44import android.app.slice.SliceManager;
45import android.content.Context;
46import android.graphics.drawable.Icon;
47import android.net.Uri;
48import android.os.Bundle;
49import android.os.Parcelable;
50
51import java.util.ArrayList;
52import java.util.Arrays;
53import java.util.List;
54
55import androidx.annotation.NonNull;
56import androidx.annotation.Nullable;
57import androidx.annotation.RequiresApi;
58import androidx.annotation.RestrictTo;
59import androidx.annotation.RestrictTo.Scope;
60import androidx.annotation.StringDef;
61import androidx.core.os.BuildCompat;
62import androidx.slice.compat.SliceProviderCompat;
63
64/**
65 * A slice is a piece of app content and actions that can be surfaced outside of the app.
66 *
67 * <p>They are constructed using {@link androidx.slice.builders.TemplateSliceBuilder}s
68 * in a tree structure that provides the OS some information about how the content should be
69 * displayed.
70 */
71public final class Slice {
72
73    private static final String HINTS = "hints";
74    private static final String ITEMS = "items";
75    private static final String URI = "uri";
76    private static final String SPEC_TYPE = "type";
77    private static final String SPEC_REVISION = "revision";
78    private final SliceSpec mSpec;
79
80    /**
81     * @hide
82     */
83    @RestrictTo(Scope.LIBRARY)
84    @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
85            HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL, HINT_SUMMARY, HINT_SEE_MORE,
86            HINT_SHORTCUT, HINT_KEY_WORDS})
87    public @interface SliceHint{ }
88
89    private final SliceItem[] mItems;
90    private final @SliceHint String[] mHints;
91    private Uri mUri;
92
93    /**
94     * @hide
95     */
96    @RestrictTo(Scope.LIBRARY)
97    Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri,
98            SliceSpec spec) {
99        mHints = hints;
100        mItems = items.toArray(new SliceItem[items.size()]);
101        mUri = uri;
102        mSpec = spec;
103    }
104
105    /**
106     * @hide
107     */
108    @RestrictTo(Scope.LIBRARY)
109    public Slice(Bundle in) {
110        mHints = in.getStringArray(HINTS);
111        Parcelable[] items = in.getParcelableArray(ITEMS);
112        mItems = new SliceItem[items.length];
113        for (int i = 0; i < mItems.length; i++) {
114            if (items[i] instanceof Bundle) {
115                mItems[i] = new SliceItem((Bundle) items[i]);
116            }
117        }
118        mUri = in.getParcelable(URI);
119        mSpec = in.containsKey(SPEC_TYPE)
120                ? new SliceSpec(in.getString(SPEC_TYPE), in.getInt(SPEC_REVISION))
121                : null;
122    }
123
124    /**
125     * @hide
126     */
127    @RestrictTo(Scope.LIBRARY)
128    public Bundle toBundle() {
129        Bundle b = new Bundle();
130        b.putStringArray(HINTS, mHints);
131        Parcelable[] p = new Parcelable[mItems.length];
132        for (int i = 0; i < mItems.length; i++) {
133            p[i] = mItems[i].toBundle();
134        }
135        b.putParcelableArray(ITEMS, p);
136        b.putParcelable(URI, mUri);
137        if (mSpec != null) {
138            b.putString(SPEC_TYPE, mSpec.getType());
139            b.putInt(SPEC_REVISION, mSpec.getRevision());
140        }
141        return b;
142    }
143
144    /**
145     * @return The spec for this slice
146     * @hide
147     */
148    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
149    public @Nullable SliceSpec getSpec() {
150        return mSpec;
151    }
152
153    /**
154     * @return The Uri that this Slice represents.
155     */
156    public Uri getUri() {
157        return mUri;
158    }
159
160    /**
161     * @return All child {@link SliceItem}s that this Slice contains.
162     */
163    public List<SliceItem> getItems() {
164        return Arrays.asList(mItems);
165    }
166
167    /**
168     * @return All hints associated with this Slice.
169     */
170    public @SliceHint List<String> getHints() {
171        return Arrays.asList(mHints);
172    }
173
174    /**
175     * @hide
176     */
177    @RestrictTo(Scope.LIBRARY_GROUP)
178    public boolean hasHint(@SliceHint String hint) {
179        return ArrayUtils.contains(mHints, hint);
180    }
181
182    /**
183     * A Builder used to construct {@link Slice}s
184     * @hide
185     */
186    @RestrictTo(Scope.LIBRARY_GROUP)
187    public static class Builder {
188
189        private final Uri mUri;
190        private ArrayList<SliceItem> mItems = new ArrayList<>();
191        private @SliceHint ArrayList<String> mHints = new ArrayList<>();
192        private SliceSpec mSpec;
193
194        /**
195         * Create a builder which will construct a {@link Slice} for the Given Uri.
196         * @param uri Uri to tag for this slice.
197         */
198        public Builder(@NonNull Uri uri) {
199            mUri = uri;
200        }
201
202        /**
203         * Create a builder for a {@link Slice} that is a sub-slice of the slice
204         * being constructed by the provided builder.
205         * @param parent The builder constructing the parent slice
206         */
207        public Builder(@NonNull Slice.Builder parent) {
208            mUri = parent.mUri.buildUpon().appendPath("_gen")
209                    .appendPath(String.valueOf(mItems.size())).build();
210        }
211
212        /**
213         * Add the spec for this slice.
214         * @hide
215         */
216        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
217        public Builder setSpec(SliceSpec spec) {
218            mSpec = spec;
219            return this;
220        }
221
222        /**
223         * Add hints to the Slice being constructed
224         */
225        public Builder addHints(@SliceHint String... hints) {
226            mHints.addAll(Arrays.asList(hints));
227            return this;
228        }
229
230        /**
231         * Add hints to the Slice being constructed
232         */
233        public Builder addHints(@SliceHint List<String> hints) {
234            return addHints(hints.toArray(new String[hints.size()]));
235        }
236
237        /**
238         * Add a sub-slice to the slice being constructed
239         */
240        public Builder addSubSlice(@NonNull Slice slice) {
241            return addSubSlice(slice, null);
242        }
243
244        /**
245         * Add a sub-slice to the slice being constructed
246         * @param subType Optional template-specific type information
247         * @see {@link SliceItem#getSubType()}
248         */
249        public Builder addSubSlice(@NonNull Slice slice, String subType) {
250            mItems.add(new SliceItem(slice, FORMAT_SLICE, subType, slice.getHints().toArray(
251                    new String[slice.getHints().size()])));
252            return this;
253        }
254
255        /**
256         * Add an action to the slice being constructed
257         * @param subType Optional template-specific type information
258         * @see {@link SliceItem#getSubType()}
259         */
260        public Slice.Builder addAction(@NonNull PendingIntent action,
261                @NonNull Slice s, @Nullable String subType) {
262            @SliceHint String[] hints = s != null
263                    ? s.getHints().toArray(new String[s.getHints().size()]) : new String[0];
264            mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, hints));
265            return this;
266        }
267
268        /**
269         * Add text to the slice being constructed
270         * @param subType Optional template-specific type information
271         * @see {@link SliceItem#getSubType()}
272         */
273        public Builder addText(CharSequence text, @Nullable String subType,
274                @SliceHint String... hints) {
275            mItems.add(new SliceItem(text, FORMAT_TEXT, subType, hints));
276            return this;
277        }
278
279        /**
280         * Add text to the slice being constructed
281         * @param subType Optional template-specific type information
282         * @see {@link SliceItem#getSubType()}
283         */
284        public Builder addText(CharSequence text, @Nullable String subType,
285                @SliceHint List<String> hints) {
286            return addText(text, subType, hints.toArray(new String[hints.size()]));
287        }
288
289        /**
290         * Add an image to the slice being constructed
291         * @param subType Optional template-specific type information
292         * @see {@link SliceItem#getSubType()}
293         */
294        public Builder addIcon(Icon icon, @Nullable String subType,
295                @SliceHint String... hints) {
296            mItems.add(new SliceItem(icon, FORMAT_IMAGE, subType, hints));
297            return this;
298        }
299
300        /**
301         * Add an image to the slice being constructed
302         * @param subType Optional template-specific type information
303         * @see {@link SliceItem#getSubType()}
304         */
305        public Builder addIcon(Icon icon, @Nullable String subType,
306                @SliceHint List<String> hints) {
307            return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
308        }
309
310        /**
311         * Add remote input to the slice being constructed
312         * @param subType Optional template-specific type information
313         * @see {@link SliceItem#getSubType()}
314         * @hide
315         */
316        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
317        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
318                @SliceHint List<String> hints) {
319            return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
320        }
321
322        /**
323         * Add remote input to the slice being constructed
324         * @param subType Optional template-specific type information
325         * @see {@link SliceItem#getSubType()}
326         * @hide
327         */
328        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
329        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
330                @SliceHint String... hints) {
331            mItems.add(new SliceItem(remoteInput, FORMAT_REMOTE_INPUT, subType, hints));
332            return this;
333        }
334
335        /**
336         * Add a int to the slice being constructed
337         * @param subType Optional template-specific type information
338         * @see {@link SliceItem#getSubType()}
339         */
340        public Builder addInt(int value, @Nullable String subType,
341                @SliceHint String... hints) {
342            mItems.add(new SliceItem(value, FORMAT_INT, subType, hints));
343            return this;
344        }
345
346        /**
347         * Add a int to the slice being constructed
348         * @param subType Optional template-specific type information
349         * @see {@link SliceItem#getSubType()}
350         */
351        public Builder addInt(int value, @Nullable String subType,
352                @SliceHint List<String> hints) {
353            return addInt(value, subType, hints.toArray(new String[hints.size()]));
354        }
355
356        /**
357         * Add a timestamp to the slice being constructed
358         * @param subType Optional template-specific type information
359         * @see {@link SliceItem#getSubType()}
360         */
361        public Slice.Builder addTimestamp(long time, @Nullable String subType,
362                @SliceHint String... hints) {
363            mItems.add(new SliceItem(time, FORMAT_TIMESTAMP, subType, hints));
364            return this;
365        }
366
367        /**
368         * Add a timestamp to the slice being constructed
369         * @param subType Optional template-specific type information
370         * @see {@link SliceItem#getSubType()}
371         */
372        public Slice.Builder addTimestamp(long time, @Nullable String subType,
373                @SliceHint List<String> hints) {
374            return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
375        }
376
377        /**
378         * Add a SliceItem to the slice being constructed.
379         * @hide
380         */
381        @RestrictTo(Scope.LIBRARY)
382        public Slice.Builder addItem(SliceItem item) {
383            mItems.add(item);
384            return this;
385        }
386
387        /**
388         * Construct the slice.
389         */
390        public Slice build() {
391            return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
392        }
393    }
394
395    /**
396     * @hide
397     * @return A string representation of this slice.
398     */
399    @RestrictTo(Scope.LIBRARY)
400    @Override
401    public String toString() {
402        return toString("");
403    }
404
405    /**
406     * @hide
407     */
408    @RestrictTo(Scope.LIBRARY)
409    public String toString(String indent) {
410        StringBuilder sb = new StringBuilder();
411        for (int i = 0; i < mItems.length; i++) {
412            sb.append(indent);
413            if (FORMAT_SLICE.equals(mItems[i].getFormat())) {
414                sb.append("slice:\n");
415                sb.append(mItems[i].getSlice().toString(indent + "   "));
416            } else if (FORMAT_ACTION.equals(mItems[i].getFormat())) {
417                sb.append("action:\n");
418                sb.append(mItems[i].getSlice().toString(indent + "   "));
419            } else if (FORMAT_TEXT.equals(mItems[i].getFormat())) {
420                sb.append("text: ");
421                sb.append(mItems[i].getText());
422                sb.append("\n");
423            } else {
424                sb.append(SliceItem.typeToString(mItems[i].getFormat()));
425                sb.append("\n");
426            }
427        }
428        return sb.toString();
429    }
430
431    /**
432     * Turns a slice Uri into slice content.
433     *
434     * @hide
435     * @param context Context to be used.
436     * @param uri The URI to a slice provider
437     * @return The Slice provided by the app or null if none is given.
438     * @see Slice
439     */
440    @RestrictTo(Scope.LIBRARY_GROUP)
441    @SuppressWarnings("NewApi") // Lint doesn't understand BuildCompat.
442    @Nullable
443    public static Slice bindSlice(Context context, @NonNull Uri uri,
444            List<SliceSpec> supportedSpecs) {
445        if (BuildCompat.isAtLeastP()) {
446            return callBindSlice(context, uri, supportedSpecs);
447        } else {
448            return SliceProviderCompat.bindSlice(context, uri, supportedSpecs);
449        }
450    }
451
452    @RequiresApi(28)
453    private static Slice callBindSlice(Context context, Uri uri,
454            List<SliceSpec> supportedSpecs) {
455        return SliceConvert.wrap(context.getSystemService(SliceManager.class)
456                .bindSlice(uri, unwrap(supportedSpecs)));
457    }
458}
459