blob: d0137b1dda1646cad4a7d4ade8cd67126b3f2ba5 [file] [log] [blame]
Jason Monkdcb5e2f2017-11-15 20:19:43 -05001/*
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.app.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_SELECTED;
27import static android.app.slice.Slice.HINT_TITLE;
28import static android.app.slice.SliceItem.FORMAT_ACTION;
Jason Monkdcb5e2f2017-11-15 20:19:43 -050029import static android.app.slice.SliceItem.FORMAT_IMAGE;
Jason Monk98ae4f82017-12-18 11:29:07 -050030import static android.app.slice.SliceItem.FORMAT_INT;
Jason Monkdcb5e2f2017-11-15 20:19:43 -050031import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
32import static android.app.slice.SliceItem.FORMAT_SLICE;
33import static android.app.slice.SliceItem.FORMAT_TEXT;
34import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
35
Jason Monk6fa0c9b2017-12-12 20:55:35 -050036import static androidx.app.slice.SliceConvert.unwrap;
37
Jason Monk2a7d0fc2017-11-15 10:10:24 -050038import android.annotation.TargetApi;
Jason Monkdcb5e2f2017-11-15 20:19:43 -050039import android.app.PendingIntent;
40import android.app.RemoteInput;
41import android.content.ContentProvider;
42import android.content.Context;
43import android.content.Intent;
44import android.graphics.drawable.Icon;
45import android.net.Uri;
46import android.os.Bundle;
47import android.os.Parcelable;
48import android.support.annotation.NonNull;
49import android.support.annotation.Nullable;
50import android.support.annotation.RestrictTo;
51import android.support.annotation.RestrictTo.Scope;
52import android.support.annotation.StringDef;
53import android.support.v4.os.BuildCompat;
54
55import java.util.ArrayList;
56import java.util.Arrays;
57import java.util.List;
58
59import androidx.app.slice.compat.SliceProviderCompat;
60import androidx.app.slice.core.SliceHints;
Jason Monkdcb5e2f2017-11-15 20:19:43 -050061
62/**
63 * A slice is a piece of app content and actions that can be surfaced outside of the app.
64 *
Jason Monk0dd353b2017-12-18 11:32:04 -050065 * <p>They are constructed using {@link androidx.app.slice.builders.TemplateSliceBuilder}s
66 * in a tree structure that provides the OS some information about how the content should be
67 * displayed.
Jason Monkdcb5e2f2017-11-15 20:19:43 -050068 */
69public final class Slice {
70
71 private static final String HINTS = "hints";
72 private static final String ITEMS = "items";
73 private static final String URI = "uri";
Jason Monk6fa0c9b2017-12-12 20:55:35 -050074 private static final String SPEC_TYPE = "type";
75 private static final String SPEC_REVISION = "revision";
76 private final SliceSpec mSpec;
Jason Monkdcb5e2f2017-11-15 20:19:43 -050077
78 /**
79 * @hide
80 */
81 @RestrictTo(Scope.LIBRARY)
82 @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
83 HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL,
Mady Mellor71ef84d2017-12-11 13:33:36 -080084 SliceHints.HINT_SUMMARY, SliceHints.SUBTYPE_TOGGLE})
Jason Monkdcb5e2f2017-11-15 20:19:43 -050085 public @interface SliceHint{ }
86
87 private final SliceItem[] mItems;
88 private final @SliceHint String[] mHints;
89 private Uri mUri;
90
91 /**
92 * @hide
93 */
94 @RestrictTo(Scope.LIBRARY)
Jason Monk6fa0c9b2017-12-12 20:55:35 -050095 Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri,
96 SliceSpec spec) {
Jason Monkdcb5e2f2017-11-15 20:19:43 -050097 mHints = hints;
98 mItems = items.toArray(new SliceItem[items.size()]);
99 mUri = uri;
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500100 mSpec = spec;
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500101 }
102
103 /**
104 * @hide
105 */
106 @RestrictTo(Scope.LIBRARY)
107 public Slice(Bundle in) {
108 mHints = in.getStringArray(HINTS);
109 Parcelable[] items = in.getParcelableArray(ITEMS);
110 mItems = new SliceItem[items.length];
111 for (int i = 0; i < mItems.length; i++) {
112 if (items[i] instanceof Bundle) {
113 mItems[i] = new SliceItem((Bundle) items[i]);
114 }
115 }
116 mUri = in.getParcelable(URI);
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500117 mSpec = in.containsKey(SPEC_TYPE)
118 ? new SliceSpec(in.getString(SPEC_TYPE), in.getInt(SPEC_REVISION))
119 : null;
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500120 }
121
122 /**
123 * @hide
124 */
125 @RestrictTo(Scope.LIBRARY)
126 public Bundle toBundle() {
127 Bundle b = new Bundle();
128 b.putStringArray(HINTS, mHints);
129 Parcelable[] p = new Parcelable[mItems.length];
130 for (int i = 0; i < mItems.length; i++) {
131 p[i] = mItems[i].toBundle();
132 }
133 b.putParcelableArray(ITEMS, p);
134 b.putParcelable(URI, mUri);
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500135 if (mSpec != null) {
136 b.putString(SPEC_TYPE, mSpec.getType());
137 b.putInt(SPEC_REVISION, mSpec.getRevision());
138 }
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500139 return b;
140 }
141
142 /**
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500143 * @return The spec for this slice
144 * @hide
145 */
146 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
147 public @Nullable SliceSpec getSpec() {
148 return mSpec;
149 }
150
151 /**
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500152 * @return The Uri that this Slice represents.
153 */
154 public Uri getUri() {
155 return mUri;
156 }
157
158 /**
159 * @return All child {@link SliceItem}s that this Slice contains.
160 */
161 public List<SliceItem> getItems() {
162 return Arrays.asList(mItems);
163 }
164
165 /**
166 * @return All hints associated with this Slice.
167 */
168 public @SliceHint List<String> getHints() {
169 return Arrays.asList(mHints);
170 }
171
172 /**
173 * @hide
174 */
175 @RestrictTo(Scope.LIBRARY_GROUP)
176 public boolean hasHint(@SliceHint String hint) {
177 return ArrayUtils.contains(mHints, hint);
178 }
179
180 /**
181 * A Builder used to construct {@link Slice}s
Jason Monk0dd353b2017-12-18 11:32:04 -0500182 * @hide
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500183 */
Jason Monk0dd353b2017-12-18 11:32:04 -0500184 @RestrictTo(Scope.LIBRARY_GROUP)
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500185 public static class Builder {
186
187 private final Uri mUri;
188 private ArrayList<SliceItem> mItems = new ArrayList<>();
189 private @SliceHint ArrayList<String> mHints = new ArrayList<>();
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500190 private SliceSpec mSpec;
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500191
192 /**
193 * Create a builder which will construct a {@link Slice} for the Given Uri.
194 * @param uri Uri to tag for this slice.
195 */
196 public Builder(@NonNull Uri uri) {
197 mUri = uri;
198 }
199
200 /**
201 * Create a builder for a {@link Slice} that is a sub-slice of the slice
202 * being constructed by the provided builder.
203 * @param parent The builder constructing the parent slice
204 */
205 public Builder(@NonNull Slice.Builder parent) {
206 mUri = parent.mUri.buildUpon().appendPath("_gen")
207 .appendPath(String.valueOf(mItems.size())).build();
208 }
209
210 /**
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500211 * Add the spec for this slice.
212 * @hide
213 */
214 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
215 public Builder setSpec(SliceSpec spec) {
216 mSpec = spec;
217 return this;
218 }
219
220 /**
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500221 * Add hints to the Slice being constructed
222 */
223 public Builder addHints(@SliceHint String... hints) {
224 mHints.addAll(Arrays.asList(hints));
225 return this;
226 }
227
228 /**
229 * Add hints to the Slice being constructed
230 */
231 public Builder addHints(@SliceHint List<String> hints) {
232 return addHints(hints.toArray(new String[hints.size()]));
233 }
234
235 /**
236 * Add a sub-slice to the slice being constructed
237 */
238 public Builder addSubSlice(@NonNull Slice slice) {
239 return addSubSlice(slice, null);
240 }
241
242 /**
243 * Add a sub-slice to the slice being constructed
244 * @param subType Optional template-specific type information
245 * @see {@link SliceItem#getSubType()}
246 */
247 public Builder addSubSlice(@NonNull Slice slice, String subType) {
248 mItems.add(new SliceItem(slice, FORMAT_SLICE, subType, slice.getHints().toArray(
249 new String[slice.getHints().size()])));
250 return this;
251 }
252
253 /**
254 * Add an action to the slice being constructed
255 * @param subType Optional template-specific type information
256 * @see {@link SliceItem#getSubType()}
257 */
258 public Slice.Builder addAction(@NonNull PendingIntent action,
259 @NonNull Slice s, @Nullable String subType) {
260 mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, new String[0]));
261 return this;
262 }
263
264 /**
265 * Add text to the slice being constructed
266 * @param subType Optional template-specific type information
267 * @see {@link SliceItem#getSubType()}
268 */
269 public Builder addText(CharSequence text, @Nullable String subType,
270 @SliceHint String... hints) {
271 mItems.add(new SliceItem(text, FORMAT_TEXT, subType, hints));
272 return this;
273 }
274
275 /**
276 * Add text to the slice being constructed
277 * @param subType Optional template-specific type information
278 * @see {@link SliceItem#getSubType()}
279 */
280 public Builder addText(CharSequence text, @Nullable String subType,
281 @SliceHint List<String> hints) {
282 return addText(text, subType, hints.toArray(new String[hints.size()]));
283 }
284
285 /**
286 * Add an image to the slice being constructed
287 * @param subType Optional template-specific type information
288 * @see {@link SliceItem#getSubType()}
289 */
290 public Builder addIcon(Icon icon, @Nullable String subType,
291 @SliceHint String... hints) {
292 mItems.add(new SliceItem(icon, FORMAT_IMAGE, subType, hints));
293 return this;
294 }
295
296 /**
297 * Add an image to the slice being constructed
298 * @param subType Optional template-specific type information
299 * @see {@link SliceItem#getSubType()}
300 */
301 public Builder addIcon(Icon icon, @Nullable String subType,
302 @SliceHint List<String> hints) {
303 return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
304 }
305
306 /**
307 * Add remote input to the slice being constructed
308 * @param subType Optional template-specific type information
309 * @see {@link SliceItem#getSubType()}
Mady Mellorc7791012018-01-16 17:25:01 -0800310 * @hide
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500311 */
Mady Mellorc7791012018-01-16 17:25:01 -0800312 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500313 public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
314 @SliceHint List<String> hints) {
315 return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
316 }
317
318 /**
319 * Add remote input to the slice being constructed
320 * @param subType Optional template-specific type information
321 * @see {@link SliceItem#getSubType()}
Mady Mellorc7791012018-01-16 17:25:01 -0800322 * @hide
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500323 */
Mady Mellorc7791012018-01-16 17:25:01 -0800324 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500325 public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
326 @SliceHint String... hints) {
327 mItems.add(new SliceItem(remoteInput, FORMAT_REMOTE_INPUT, subType, hints));
328 return this;
329 }
330
331 /**
Jason Monk98ae4f82017-12-18 11:29:07 -0500332 * Add a int to the slice being constructed
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500333 * @param subType Optional template-specific type information
334 * @see {@link SliceItem#getSubType()}
335 */
Jason Monk98ae4f82017-12-18 11:29:07 -0500336 public Builder addInt(int value, @Nullable String subType,
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500337 @SliceHint String... hints) {
Jason Monk98ae4f82017-12-18 11:29:07 -0500338 mItems.add(new SliceItem(value, FORMAT_INT, subType, hints));
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500339 return this;
340 }
341
342 /**
Jason Monk98ae4f82017-12-18 11:29:07 -0500343 * Add a int to the slice being constructed
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500344 * @param subType Optional template-specific type information
345 * @see {@link SliceItem#getSubType()}
346 */
Jason Monk98ae4f82017-12-18 11:29:07 -0500347 public Builder addInt(int value, @Nullable String subType,
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500348 @SliceHint List<String> hints) {
Jason Monk98ae4f82017-12-18 11:29:07 -0500349 return addInt(value, subType, hints.toArray(new String[hints.size()]));
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500350 }
351
352 /**
353 * Add a timestamp to the slice being constructed
354 * @param subType Optional template-specific type information
355 * @see {@link SliceItem#getSubType()}
356 */
357 public Slice.Builder addTimestamp(long time, @Nullable String subType,
358 @SliceHint String... hints) {
359 mItems.add(new SliceItem(time, FORMAT_TIMESTAMP, subType, hints));
360 return this;
361 }
362
363 /**
364 * Add a timestamp to the slice being constructed
365 * @param subType Optional template-specific type information
366 * @see {@link SliceItem#getSubType()}
367 */
368 public Slice.Builder addTimestamp(long time, @Nullable String subType,
369 @SliceHint List<String> hints) {
370 return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
371 }
372
373 /**
Mady Mellorc1334182017-11-10 15:50:35 -0800374 * Add a SliceItem to the slice being constructed.
375 * @hide
376 */
377 @RestrictTo(Scope.LIBRARY)
378 public Slice.Builder addItem(SliceItem item) {
379 mItems.add(item);
380 return this;
381 }
382
383 /**
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500384 * Construct the slice.
385 */
386 public Slice build() {
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500387 return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500388 }
389 }
390
391 /**
392 * @hide
393 * @return A string representation of this slice.
394 */
395 @RestrictTo(Scope.LIBRARY)
396 @Override
397 public String toString() {
398 return toString("");
399 }
400
Mady Mellor6b5cd612017-12-14 11:36:59 -0800401 /**
402 * @hide
403 */
404 @RestrictTo(Scope.LIBRARY)
405 public String toString(String indent) {
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500406 StringBuilder sb = new StringBuilder();
407 for (int i = 0; i < mItems.length; i++) {
408 sb.append(indent);
409 if (FORMAT_SLICE.equals(mItems[i].getFormat())) {
410 sb.append("slice:\n");
411 sb.append(mItems[i].getSlice().toString(indent + " "));
Jason Monk901e2a62017-12-18 15:40:24 -0500412 } else if (FORMAT_ACTION.equals(mItems[i].getFormat())) {
413 sb.append("action:\n");
414 sb.append(mItems[i].getSlice().toString(indent + " "));
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500415 } else if (FORMAT_TEXT.equals(mItems[i].getFormat())) {
416 sb.append("text: ");
417 sb.append(mItems[i].getText());
418 sb.append("\n");
419 } else {
420 sb.append(SliceItem.typeToString(mItems[i].getFormat()));
421 sb.append("\n");
422 }
423 }
424 return sb.toString();
425 }
426
427 /**
Jason Monka09cb672018-01-08 13:17:36 -0500428 */
429 public static @Nullable Slice bindSlice(Context context, @NonNull Uri uri) {
430 throw new RuntimeException("Stub, to be removed");
431 }
432
433 /**
434 */
435 public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
436 throw new RuntimeException("Stub, to be removed");
437 }
438
439 /**
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500440 * Turns a slice Uri into slice content.
441 *
Jason Monka09cb672018-01-08 13:17:36 -0500442 * @hide
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500443 * @param context Context to be used.
444 * @param uri The URI to a slice provider
445 * @return The Slice provided by the app or null if none is given.
446 * @see Slice
447 */
Jason Monka09cb672018-01-08 13:17:36 -0500448 @RestrictTo(Scope.LIBRARY_GROUP)
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500449 @SuppressWarnings("NewApi")
Jason Monka09cb672018-01-08 13:17:36 -0500450 public static @Nullable Slice bindSlice(Context context, @NonNull Uri uri,
451 List<SliceSpec> supportedSpecs) {
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500452 if (BuildCompat.isAtLeastP()) {
Jason Monka09cb672018-01-08 13:17:36 -0500453 return callBindSlice(context, uri, supportedSpecs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500454 } else {
Jason Monka09cb672018-01-08 13:17:36 -0500455 return SliceProviderCompat.bindSlice(context, uri, supportedSpecs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500456 }
457 }
458
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500459 @TargetApi(28)
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500460 private static Slice callBindSlice(Context context, Uri uri,
461 List<SliceSpec> supportedSpecs) {
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500462 return SliceConvert.wrap(android.app.slice.Slice.bindSlice(
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500463 context.getContentResolver(), uri, unwrap(supportedSpecs)));
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500464 }
465
466
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500467 /**
468 * Turns a slice intent into slice content. Expects an explicit intent. If there is no
469 * {@link ContentProvider} associated with the given intent this will throw
470 * {@link IllegalArgumentException}.
471 *
Jason Monka09cb672018-01-08 13:17:36 -0500472 * @hide
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500473 * @param context The context to use.
474 * @param intent The intent associated with a slice.
475 * @return The Slice provided by the app or null if none is given.
476 * @see Slice
477 * @see SliceProvider#onMapIntentToUri(Intent)
478 * @see Intent
479 */
Jason Monka09cb672018-01-08 13:17:36 -0500480 @RestrictTo(Scope.LIBRARY_GROUP)
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500481 @SuppressWarnings("NewApi")
Jason Monka09cb672018-01-08 13:17:36 -0500482 public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent,
483 List<SliceSpec> supportedSpecs) {
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500484 if (BuildCompat.isAtLeastP()) {
Jason Monka09cb672018-01-08 13:17:36 -0500485 return callBindSlice(context, intent, supportedSpecs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500486 } else {
Jason Monka09cb672018-01-08 13:17:36 -0500487 return SliceProviderCompat.bindSlice(context, intent, supportedSpecs);
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500488 }
489 }
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500490
491 @TargetApi(28)
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500492 private static Slice callBindSlice(Context context, Intent intent,
493 List<SliceSpec> supportedSpecs) {
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500494 return SliceConvert.wrap(android.app.slice.Slice.bindSlice(
Jason Monk6fa0c9b2017-12-12 20:55:35 -0500495 context, intent, unwrap(supportedSpecs)));
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500496 }
Jason Monkdcb5e2f2017-11-15 20:19:43 -0500497}