• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.tv.twopanelsettings.slices.compat;
18 
19 import static android.app.slice.Slice.HINT_ACTIONS;
20 import static android.app.slice.Slice.HINT_ERROR;
21 import static android.app.slice.Slice.HINT_HORIZONTAL;
22 import static android.app.slice.Slice.HINT_KEYWORDS;
23 import static android.app.slice.Slice.HINT_LARGE;
24 import static android.app.slice.Slice.HINT_LAST_UPDATED;
25 import static android.app.slice.Slice.HINT_LIST;
26 import static android.app.slice.Slice.HINT_LIST_ITEM;
27 import static android.app.slice.Slice.HINT_NO_TINT;
28 import static android.app.slice.Slice.HINT_PARTIAL;
29 import static android.app.slice.Slice.HINT_PERMISSION_REQUEST;
30 import static android.app.slice.Slice.HINT_SEE_MORE;
31 import static android.app.slice.Slice.HINT_SELECTED;
32 import static android.app.slice.Slice.HINT_SHORTCUT;
33 import static android.app.slice.Slice.HINT_SUMMARY;
34 import static android.app.slice.Slice.HINT_TITLE;
35 import static android.app.slice.Slice.HINT_TTL;
36 import static android.app.slice.SliceItem.FORMAT_ACTION;
37 import static android.app.slice.SliceItem.FORMAT_IMAGE;
38 import static android.app.slice.SliceItem.FORMAT_INT;
39 import static android.app.slice.SliceItem.FORMAT_LONG;
40 import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
41 import static android.app.slice.SliceItem.FORMAT_SLICE;
42 import static android.app.slice.SliceItem.FORMAT_TEXT;
43 import static com.android.tv.twopanelsettings.slices.compat.SliceConvert.unwrap;
44 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_ACTIVITY;
45 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_CACHED;
46 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_END_OF_SECTION;
47 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_OVERLAY;
48 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_RAW;
49 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_SELECTION_OPTION;
50 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_SHOW_LABEL;
51 
52 import android.app.RemoteInput;
53 import android.content.Context;
54 import android.graphics.drawable.Icon;
55 import android.net.Uri;
56 import android.os.Bundle;
57 import android.os.Parcelable;
58 import androidx.annotation.NonNull;
59 import androidx.annotation.Nullable;
60 import androidx.annotation.StringDef;
61 import androidx.core.graphics.drawable.IconCompat;
62 import androidx.core.util.Preconditions;
63 import com.android.tv.twopanelsettings.slices.base.SliceManager;
64 import java.lang.annotation.Retention;
65 import java.lang.annotation.RetentionPolicy;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.List;
69 import java.util.Set;
70 
71 /**
72  * A slice is a piece of app content and actions that can be surfaced outside of the app. A slice is
73  * identified by a Uri and served via a {@link SliceProvider}.
74  *
75  * <p>Slices are constructed using {@link
76  * com.android.tv.twopanelsettings.slices.compat.builders.TemplateSliceBuilder}s in a tree structure
77  * that provides the OS some information about how the content should be displayed.
78  *
79  * <p>Slice framework has been deprecated, it will not receive any updates moving forward. If you
80  * are looking for a framework that handles communication across apps, consider using {@link
81  * android.app.appsearch.AppSearchManager}.
82  */
83 // @Deprecated // Supported for TV
84 public final class Slice {
85 
86   /** Key to retrieve an extra added to an intent when an item in a selection is selected. */
87   public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION";
88 
89   /**
90    * Subtype to tag an item as representing the progress bar mode for a {@link
91    * android.app.slice.Slice#SUBTYPE_RANGE}
92    */
93   // @RestrictTo(Scope.LIBRARY_GROUP)
94   public static final String SUBTYPE_RANGE_MODE = "range_mode";
95 
96   private static final String HINTS = "hints";
97   private static final String ITEMS = "items";
98   private static final String URI = "uri";
99   private static final String SPEC_TYPE = "type";
100   private static final String SPEC_REVISION = "revision";
101 
102   static final String[] NO_HINTS = new String[0];
103   static final SliceItem[] NO_ITEMS = new SliceItem[0];
104 
105   /** */
106   // @RestrictTo(Scope.LIBRARY)
107   @StringDef({
108     HINT_TITLE,
109     HINT_LIST,
110     HINT_LIST_ITEM,
111     HINT_LARGE,
112     HINT_ACTIONS,
113     HINT_SELECTED,
114     HINT_HORIZONTAL,
115     HINT_NO_TINT,
116     HINT_PARTIAL,
117     HINT_SUMMARY,
118     HINT_SEE_MORE,
119     HINT_SHORTCUT,
120     HINT_KEYWORDS,
121     HINT_TTL,
122     HINT_LAST_UPDATED,
123     HINT_PERMISSION_REQUEST,
124     HINT_ERROR,
125     HINT_ACTIVITY,
126     HINT_CACHED,
127     HINT_END_OF_SECTION,
128     HINT_SELECTION_OPTION,
129     HINT_RAW,
130     HINT_OVERLAY,
131     HINT_SHOW_LABEL
132   })
133   @Retention(RetentionPolicy.SOURCE)
134   public @interface SliceHint {}
135 
136   SliceSpec mSpec = null;
137 
138   SliceItem[] mItems = NO_ITEMS;
139 
140   @SliceHint
141   String[] mHints = NO_HINTS;
142 
143   String mUri = null;
144 
145   /** */
146   // @RestrictTo(Scope.LIBRARY)
Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec)147   Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec) {
148     mHints = hints;
149     mItems = items.toArray(new SliceItem[items.size()]);
150     mUri = uri.toString();
151     mSpec = spec;
152   }
153 
154   /** */
155   // @RestrictTo(Scope.LIBRARY)
156   @SuppressWarnings("deprecation")
Slice(@onNull Bundle in)157   public Slice(@NonNull Bundle in) {
158     mHints = in.getStringArray(HINTS);
159     Parcelable[] items = in.getParcelableArray(ITEMS);
160     mItems = new SliceItem[items.length];
161     for (int i = 0; i < mItems.length; i++) {
162       if (items[i] instanceof Bundle) {
163         mItems[i] = new SliceItem((Bundle) items[i]);
164       }
165     }
166     mUri = in.getParcelable(URI).toString();
167     mSpec =
168         in.containsKey(SPEC_TYPE)
169             ? new SliceSpec(in.getString(SPEC_TYPE), in.getInt(SPEC_REVISION))
170             : null;
171   }
172 
173   /** */
174   // @RestrictTo(Scope.LIBRARY)
toBundle()175   public @NonNull Bundle toBundle() {
176     Bundle b = new Bundle();
177     b.putStringArray(HINTS, mHints);
178     Parcelable[] p = new Parcelable[mItems.length];
179     for (int i = 0; i < mItems.length; i++) {
180       p[i] = mItems[i].toBundle();
181     }
182     b.putParcelableArray(ITEMS, p);
183     b.putParcelable(URI, Uri.parse(mUri));
184     if (mSpec != null) {
185       b.putString(SPEC_TYPE, mSpec.getType());
186       b.putInt(SPEC_REVISION, mSpec.getRevision());
187     }
188     return b;
189   }
190 
191   /**
192    * @return The spec for this slice
193    */
194   // @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
getSpec()195   public @Nullable SliceSpec getSpec() {
196     return mSpec;
197   }
198 
199   /**
200    * @return The Uri that this Slice represents.
201    */
getUri()202   public @NonNull Uri getUri() {
203     return Uri.parse(mUri);
204   }
205 
206   /**
207    * @return All child {@link SliceItem}s that this Slice contains.
208    */
getItems()209   public @NonNull List<SliceItem> getItems() {
210     return Arrays.asList(mItems);
211   }
212 
213   /**
214    * @return
215    */
216   // @RestrictTo(LIBRARY)
getItemArray()217   public @NonNull SliceItem[] getItemArray() {
218     return mItems;
219   }
220 
221   /**
222    * @return All hints associated with this Slice.
223    */
getHints()224   public @NonNull @SliceHint List<String> getHints() {
225     return Arrays.asList(mHints);
226   }
227 
228   /** */
229   // @RestrictTo(LIBRARY)
getHintArray()230   public @NonNull @SliceHint String[] getHintArray() {
231     return mHints;
232   }
233 
234   /** */
235   // @RestrictTo(Scope.LIBRARY_GROUP_PREFIX)
hasHint(@onNull @liceHint String hint)236   public boolean hasHint(@NonNull @SliceHint String hint) {
237     return ArrayUtils.contains(mHints, hint);
238   }
239 
240   /** A Builder used to construct {@link Slice}s */
241   // @RestrictTo(Scope.LIBRARY_GROUP_PREFIX)
242   public static class Builder {
243 
244     private final Uri mUri;
245     private ArrayList<SliceItem> mItems = new ArrayList<>();
246     private @SliceHint ArrayList<String> mHints = new ArrayList<>();
247     private SliceSpec mSpec;
248     private int mChildId;
249 
250     /**
251      * Create a builder which will construct a {@link Slice} for the Given Uri.
252      *
253      * @param uri Uri to tag for this slice.
254      */
Builder(@onNull Uri uri)255     public Builder(@NonNull Uri uri) {
256       mUri = uri;
257     }
258 
259     /**
260      * Create a builder for a {@link Slice} that is a sub-slice of the slice being constructed by
261      * the provided builder.
262      *
263      * @param parent The builder constructing the parent slice
264      */
Builder(@onNull Builder parent)265     public Builder(@NonNull Builder parent) {
266       mUri = parent.getChildUri();
267     }
268 
getChildUri()269     private Uri getChildUri() {
270       return mUri.buildUpon().appendPath("_gen").appendPath(String.valueOf(mChildId++)).build();
271     }
272 
273     /** Add the spec for this slice. */
274     // @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
setSpec(@ullable SliceSpec spec)275     public @NonNull Builder setSpec(@Nullable SliceSpec spec) {
276       mSpec = spec;
277       return this;
278     }
279 
280     /** Add hints to the Slice being constructed */
addHints(@onNull @liceHint String... hints)281     public @NonNull Builder addHints(@NonNull @SliceHint String... hints) {
282       mHints.addAll(Arrays.asList(hints));
283       return this;
284     }
285 
286     /** Add hints to the Slice being constructed */
addHints(@onNull @liceHint List<String> hints)287     public @NonNull Builder addHints(@NonNull @SliceHint List<String> hints) {
288       return addHints(hints.toArray(new String[hints.size()]));
289     }
290 
291     /** Add a sub-slice to the slice being constructed */
addSubSlice(@onNull Slice slice)292     public @NonNull Builder addSubSlice(@NonNull Slice slice) {
293       Preconditions.checkNotNull(slice);
294       return addSubSlice(slice, null);
295     }
296 
297     /**
298      * Add a sub-slice to the slice being constructed
299      *
300      * @param subType Optional template-specific type information
301      * @see SliceItem#getSubType()
302      */
addSubSlice(@onNull Slice slice, @Nullable String subType)303     public @NonNull Builder addSubSlice(@NonNull Slice slice, @Nullable String subType) {
304       Preconditions.checkNotNull(slice);
305       mItems.add(new SliceItem(slice, FORMAT_SLICE, subType, slice.getHintArray()));
306       return this;
307     }
308 
309     /**
310      * Add an action to the slice being constructed
311      *
312      * @param subType Optional template-specific type information
313      * @see SliceItem#getSubType()
314      */
addAction( @onNull Parcelable action, @NonNull Slice s, @Nullable String subType)315     public @NonNull Builder addAction(
316         @NonNull Parcelable action, @NonNull Slice s, @Nullable String subType) {
317       Preconditions.checkNotNull(action);
318       Preconditions.checkNotNull(s);
319       @SliceHint String[] hints = s.getHintArray();
320       mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, hints));
321       return this;
322     }
323 
324     /**
325      * Add an action to the slice being constructed
326      *
327      * @param subType Optional template-specific type information
328      * @param action Callback to be triggered when a pending intent would normally be fired.
329      * @see SliceItem#getSubType()
330      */
addAction( @onNull Slice s, @Nullable String subType, @NonNull SliceItem.ActionHandler action)331     public @NonNull Builder addAction(
332         @NonNull Slice s, @Nullable String subType, @NonNull SliceItem.ActionHandler action) {
333       Preconditions.checkNotNull(s);
334       @SliceHint String[] hints = s.getHintArray();
335       mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, hints));
336       return this;
337     }
338 
339     /**
340      * Add text to the slice being constructed
341      *
342      * @param subType Optional template-specific type information
343      * @see SliceItem#getSubType()
344      */
addText( @ullable CharSequence text, @Nullable String subType, @NonNull @SliceHint String... hints)345     public @NonNull Builder addText(
346         @Nullable CharSequence text,
347         @Nullable String subType,
348         @NonNull @SliceHint String... hints) {
349       mItems.add(new SliceItem(text, FORMAT_TEXT, subType, hints));
350       return this;
351     }
352 
353     /**
354      * Add text to the slice being constructed
355      *
356      * @param subType Optional template-specific type information
357      * @see SliceItem#getSubType()
358      */
addText( @ullable CharSequence text, @Nullable String subType, @NonNull @SliceHint List<String> hints)359     public @NonNull Builder addText(
360         @Nullable CharSequence text,
361         @Nullable String subType,
362         @NonNull @SliceHint List<String> hints) {
363       return addText(text, subType, hints.toArray(new String[hints.size()]));
364     }
365 
366     /**
367      * Add an image to the slice being constructed
368      *
369      * @param subType Optional template-specific type information
370      * @see SliceItem#getSubType()
371      */
addIcon( @onNull IconCompat icon, @Nullable String subType, @NonNull @SliceHint String... hints)372     public @NonNull Builder addIcon(
373         @NonNull IconCompat icon, @Nullable String subType, @NonNull @SliceHint String... hints) {
374       Preconditions.checkNotNull(icon);
375       if (isValidIcon(icon)) {
376         mItems.add(new SliceItem(icon, FORMAT_IMAGE, subType, hints));
377       }
378       return this;
379     }
380 
381     /**
382      * Add an image to the slice being constructed
383      *
384      * @param subType Optional template-specific type information
385      * @see SliceItem#getSubType()
386      */
addIcon( @onNull IconCompat icon, @Nullable String subType, @NonNull @SliceHint List<String> hints)387     public @NonNull Builder addIcon(
388         @NonNull IconCompat icon,
389         @Nullable String subType,
390         @NonNull @SliceHint List<String> hints) {
391       Preconditions.checkNotNull(icon);
392       if (isValidIcon(icon)) {
393         return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
394       }
395       return this;
396     }
397 
398     /**
399      * Add remote input to the slice being constructed
400      *
401      * @param subType Optional template-specific type information
402      * @see SliceItem#getSubType()
403      */
404     // @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
addRemoteInput( @onNull RemoteInput remoteInput, @Nullable String subType, @NonNull @SliceHint List<String> hints)405     public @NonNull Builder addRemoteInput(
406         @NonNull RemoteInput remoteInput,
407         @Nullable String subType,
408         @NonNull @SliceHint List<String> hints) {
409       Preconditions.checkNotNull(remoteInput);
410       return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
411     }
412 
413     /**
414      * Add remote input to the slice being constructed
415      *
416      * @param subType Optional template-specific type information
417      * @see SliceItem#getSubType()
418      */
419     // @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
addRemoteInput( @onNull RemoteInput remoteInput, @Nullable String subType, @NonNull @SliceHint String... hints)420     public @NonNull Builder addRemoteInput(
421         @NonNull RemoteInput remoteInput,
422         @Nullable String subType,
423         @NonNull @SliceHint String... hints) {
424       Preconditions.checkNotNull(remoteInput);
425       mItems.add(new SliceItem(remoteInput, FORMAT_REMOTE_INPUT, subType, hints));
426       return this;
427     }
428 
429     /**
430      * Add a int to the slice being constructed
431      *
432      * @param subType Optional template-specific type information
433      * @see SliceItem#getSubType()
434      */
addInt( int value, @Nullable String subType, @NonNull @SliceHint String... hints)435     public @NonNull Builder addInt(
436         int value, @Nullable String subType, @NonNull @SliceHint String... hints) {
437       mItems.add(new SliceItem(value, FORMAT_INT, subType, hints));
438       return this;
439     }
440 
441     /**
442      * Add a int to the slice being constructed
443      *
444      * @param subType Optional template-specific type information
445      * @see SliceItem#getSubType()
446      */
addInt( int value, @Nullable String subType, @NonNull @SliceHint List<String> hints)447     public @NonNull Builder addInt(
448         int value, @Nullable String subType, @NonNull @SliceHint List<String> hints) {
449       return addInt(value, subType, hints.toArray(new String[hints.size()]));
450     }
451 
452     /**
453      * Add a long to the slice being constructed
454      *
455      * @param subType Optional template-specific type information
456      * @see SliceItem#getSubType()
457      */
addLong( long time, @Nullable String subType, @NonNull @SliceHint String... hints)458     public @NonNull Builder addLong(
459         long time, @Nullable String subType, @NonNull @SliceHint String... hints) {
460       mItems.add(new SliceItem(time, FORMAT_LONG, subType, hints));
461       return this;
462     }
463 
464     /**
465      * Add a long to the slice being constructed
466      *
467      * @param subType Optional template-specific type information
468      * @see SliceItem#getSubType()
469      */
addLong( long time, @Nullable String subType, @NonNull @SliceHint List<String> hints)470     public @NonNull Builder addLong(
471         long time, @Nullable String subType, @NonNull @SliceHint List<String> hints) {
472       return addLong(time, subType, hints.toArray(new String[hints.size()]));
473     }
474 
475     /**
476      * Add a timestamp to the slice being constructed
477      *
478      * @param subType Optional template-specific type information
479      * @see SliceItem#getSubType() TO BE REMOVED
480      */
481     // @Deprecated // Supported for TV
addTimestamp(long time, @Nullable String subType, @SliceHint String... hints)482     public Builder addTimestamp(long time, @Nullable String subType, @SliceHint String... hints) {
483       mItems.add(new SliceItem(time, FORMAT_LONG, subType, hints));
484       return this;
485     }
486 
487     /**
488      * Add a timestamp to the slice being constructed
489      *
490      * @param subType Optional template-specific type information
491      * @see SliceItem#getSubType()
492      */
addTimestamp( long time, @Nullable String subType, @NonNull @SliceHint List<String> hints)493     public @NonNull Builder addTimestamp(
494         long time, @Nullable String subType, @NonNull @SliceHint List<String> hints) {
495       return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
496     }
497 
498     /** Add a SliceItem to the slice being constructed. */
499     // @RestrictTo(Scope.LIBRARY_GROUP)
addItem(@onNull SliceItem item)500     public @NonNull Builder addItem(@NonNull SliceItem item) {
501       mItems.add(item);
502       return this;
503     }
504 
505     /** Construct the slice. */
build()506     public @NonNull Slice build() {
507       return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
508     }
509   }
510 
511   /**
512    * @return A string representation of this slice.
513    */
514   @NonNull
515   @Override
toString()516   public String toString() {
517     return toString("");
518   }
519 
520   /**
521    * @return A string representation of this slice.
522    */
523   @NonNull
524   // @RestrictTo(Scope.LIBRARY)
toString(@onNull String indent)525   public String toString(@NonNull String indent) {
526     StringBuilder sb = new StringBuilder();
527     sb.append(indent);
528     sb.append("Slice ");
529     if (mHints.length > 0) {
530       appendHints(sb, mHints);
531       sb.append(' ');
532     }
533     sb.append('[');
534     sb.append(mUri);
535     sb.append("] {\n");
536     final String nextIndent = indent + "  ";
537     for (int i = 0; i < mItems.length; i++) {
538       SliceItem item = mItems[i];
539       sb.append(item.toString(nextIndent));
540     }
541     sb.append(indent);
542     sb.append('}');
543     return sb.toString();
544   }
545 
546   /** */
547   // @RestrictTo(Scope.LIBRARY)
appendHints(@onNull StringBuilder sb, @Nullable String[] hints)548   public static void appendHints(@NonNull StringBuilder sb, @Nullable String[] hints) {
549     if (hints == null || hints.length == 0) {
550       return;
551     }
552 
553     sb.append('(');
554     int end = hints.length - 1;
555     for (int i = 0; i < end; i++) {
556       sb.append(hints[i]);
557       sb.append(", ");
558     }
559     sb.append(hints[end]);
560     sb.append(")");
561   }
562 
563   /**
564    * Turns a slice Uri into slice content.
565    *
566    * @param context Context to be used.
567    * @param uri The URI to a slice provider
568    * @return The Slice provided by the app or null if none is given.
569    * @see Slice
570    */
571   // @RestrictTo(Scope.LIBRARY_GROUP_PREFIX)
572   @Nullable
bindSlice( @onNull Context context, @NonNull Uri uri, @Nullable Set<SliceSpec> supportedSpecs)573   public static Slice bindSlice(
574       @NonNull Context context, @NonNull Uri uri, @Nullable Set<SliceSpec> supportedSpecs) {
575     return SliceViewManagerWrapper.bindSlice(
576         context, SliceManager.from(context), uri, unwrap(supportedSpecs));
577   }
578 
579   /** */
580   // @RestrictTo(LIBRARY)
isValidIcon(IconCompat icon)581   static boolean isValidIcon(IconCompat icon) {
582     if (icon == null) {
583       return false;
584     }
585     if (icon.mType == Icon.TYPE_RESOURCE && icon.getResId() == 0) {
586       throw new IllegalArgumentException(
587           "Failed to add icon, invalid resource id: " + icon.getResId());
588     }
589     return true;
590   }
591 }
592