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