Home | History | Annotate | Download | only in slice
      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 android.app.slice;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.annotation.StringDef;
     22 import android.app.PendingIntent;
     23 import android.app.RemoteInput;
     24 import android.graphics.drawable.Icon;
     25 import android.net.Uri;
     26 import android.os.Bundle;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 
     30 import com.android.internal.util.ArrayUtils;
     31 import com.android.internal.util.Preconditions;
     32 
     33 import java.lang.annotation.Retention;
     34 import java.lang.annotation.RetentionPolicy;
     35 import java.util.ArrayList;
     36 import java.util.Arrays;
     37 import java.util.List;
     38 import java.util.Objects;
     39 
     40 /**
     41  * A slice is a piece of app content and actions that can be surfaced outside of the app.
     42  *
     43  * <p>They are constructed using {@link Builder} in a tree structure
     44  * that provides the OS some information about how the content should be displayed.
     45  */
     46 public final class Slice implements Parcelable {
     47 
     48     /**
     49      * @hide
     50      */
     51     @StringDef(prefix = { "HINT_" }, value = {
     52             HINT_TITLE,
     53             HINT_LIST,
     54             HINT_LIST_ITEM,
     55             HINT_LARGE,
     56             HINT_ACTIONS,
     57             HINT_SELECTED,
     58             HINT_NO_TINT,
     59             HINT_SHORTCUT,
     60             HINT_TOGGLE,
     61             HINT_HORIZONTAL,
     62             HINT_PARTIAL,
     63             HINT_SEE_MORE,
     64             HINT_KEYWORDS,
     65             HINT_ERROR,
     66             HINT_TTL,
     67             HINT_LAST_UPDATED,
     68             HINT_PERMISSION_REQUEST,
     69     })
     70     @Retention(RetentionPolicy.SOURCE)
     71     public @interface SliceHint {}
     72     /**
     73      * @hide
     74      */
     75     @StringDef(prefix = { "SUBTYPE_" }, value = {
     76             SUBTYPE_COLOR,
     77             SUBTYPE_CONTENT_DESCRIPTION,
     78             SUBTYPE_MAX,
     79             SUBTYPE_MESSAGE,
     80             SUBTYPE_PRIORITY,
     81             SUBTYPE_RANGE,
     82             SUBTYPE_SOURCE,
     83             SUBTYPE_TOGGLE,
     84             SUBTYPE_VALUE,
     85             SUBTYPE_LAYOUT_DIRECTION,
     86     })
     87     @Retention(RetentionPolicy.SOURCE)
     88     public @interface SliceSubtype {}
     89 
     90     /**
     91      * Hint that this content is a title of other content in the slice. This can also indicate that
     92      * the content should be used in the shortcut representation of the slice (icon, label, action),
     93      * normally this should be indicated by adding the hint on the action containing that content.
     94      *
     95      * @see SliceItem#FORMAT_ACTION
     96      */
     97     public static final String HINT_TITLE       = "title";
     98     /**
     99      * Hint that all sub-items/sub-slices within this content should be considered
    100      * to have {@link #HINT_LIST_ITEM}.
    101      */
    102     public static final String HINT_LIST        = "list";
    103     /**
    104      * Hint that this item is part of a list and should be formatted as if is part
    105      * of a list.
    106      */
    107     public static final String HINT_LIST_ITEM   = "list_item";
    108     /**
    109      * Hint that this content is important and should be larger when displayed if
    110      * possible.
    111      */
    112     public static final String HINT_LARGE       = "large";
    113     /**
    114      * Hint that this slice contains a number of actions that can be grouped together
    115      * in a sort of controls area of the UI.
    116      */
    117     public static final String HINT_ACTIONS     = "actions";
    118     /**
    119      * Hint indicating that this item (and its sub-items) are the current selection.
    120      */
    121     public static final String HINT_SELECTED    = "selected";
    122     /**
    123      * Hint to indicate that this content should not be tinted.
    124      */
    125     public static final String HINT_NO_TINT     = "no_tint";
    126     /**
    127      * Hint to indicate that this content should only be displayed if the slice is presented
    128      * as a shortcut.
    129      */
    130     public static final String HINT_SHORTCUT = "shortcut";
    131     /**
    132      * Hint indicating this content should be shown instead of the normal content when the slice
    133      * is in small format.
    134      */
    135     public static final String HINT_SUMMARY = "summary";
    136     /**
    137      * Hint to indicate that this content has a toggle action associated with it. To indicate that
    138      * the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the intent
    139      * associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} which can be
    140      * retrieved to see the new state of the toggle.
    141      * @hide
    142      */
    143     public static final String HINT_TOGGLE = "toggle";
    144     /**
    145      * Hint that list items within this slice or subslice would appear better
    146      * if organized horizontally.
    147      */
    148     public static final String HINT_HORIZONTAL = "horizontal";
    149     /**
    150      * Hint to indicate that this slice is incomplete and an update will be sent once
    151      * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
    152      * OS and should not be cached by apps.
    153      */
    154     public static final String HINT_PARTIAL     = "partial";
    155     /**
    156      * A hint representing that this item should be used to indicate that there's more
    157      * content associated with this slice.
    158      */
    159     public static final String HINT_SEE_MORE = "see_more";
    160     /**
    161      * @see Builder#setCallerNeeded
    162      * @hide
    163      */
    164     public static final String HINT_CALLER_NEEDED = "caller_needed";
    165     /**
    166      * A hint to indicate that the contents of this subslice represent a list of keywords
    167      * related to the parent slice.
    168      * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE}.
    169      */
    170     public static final String HINT_KEYWORDS = "keywords";
    171     /**
    172      * A hint to indicate that this slice represents an error.
    173      */
    174     public static final String HINT_ERROR = "error";
    175     /**
    176      * Hint indicating an item representing a time-to-live for the content.
    177      */
    178     public static final String HINT_TTL = "ttl";
    179     /**
    180      * Hint indicating an item representing when the content was created or last updated.
    181      */
    182     public static final String HINT_LAST_UPDATED = "last_updated";
    183     /**
    184      * A hint to indicate that this slice represents a permission request for showing
    185      * slices.
    186      */
    187     public static final String HINT_PERMISSION_REQUEST = "permission_request";
    188     /**
    189      * Subtype to indicate that this item indicates the layout direction for content
    190      * in the slice.
    191      * Expected to be an item of format {@link SliceItem#FORMAT_INT}.
    192      */
    193     public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction";
    194     /**
    195      * Key to retrieve an extra added to an intent when a control is changed.
    196      */
    197     public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
    198     /**
    199      * Key to retrieve an extra added to an intent when the value of a slider is changed.
    200      * @deprecated remove once support lib is update to use EXTRA_RANGE_VALUE instead
    201      * @removed
    202      */
    203     @Deprecated
    204     public static final String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE";
    205     /**
    206      * Key to retrieve an extra added to an intent when the value of an input range is changed.
    207      */
    208     public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE";
    209     /**
    210      * Subtype to indicate that this is a message as part of a communication
    211      * sequence in this slice.
    212      * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE}.
    213      */
    214     public static final String SUBTYPE_MESSAGE = "message";
    215     /**
    216      * Subtype to tag the source (i.e. sender) of a {@link #SUBTYPE_MESSAGE}.
    217      * Expected to be on an item of format {@link SliceItem#FORMAT_TEXT},
    218      * {@link SliceItem#FORMAT_IMAGE} or an {@link SliceItem#FORMAT_SLICE} containing them.
    219      */
    220     public static final String SUBTYPE_SOURCE = "source";
    221     /**
    222      * Subtype to tag an item as representing a color.
    223      * Expected to be on an item of format {@link SliceItem#FORMAT_INT}.
    224      */
    225     public static final String SUBTYPE_COLOR = "color";
    226     /**
    227      * Subtype to tag an item as representing a slider.
    228      * @deprecated remove once support lib is update to use SUBTYPE_RANGE instead
    229      * @removed
    230      */
    231     @Deprecated
    232     public static final String SUBTYPE_SLIDER = "slider";
    233     /**
    234      * Subtype to tag an item as representing a range.
    235      * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE} containing
    236      * a {@link #SUBTYPE_VALUE} and possibly a {@link #SUBTYPE_MAX}.
    237      */
    238     public static final String SUBTYPE_RANGE = "range";
    239     /**
    240      * Subtype to tag an item as representing the max int value for a {@link #SUBTYPE_RANGE}.
    241      * Expected to be on an item of format {@link SliceItem#FORMAT_INT}.
    242      */
    243     public static final String SUBTYPE_MAX = "max";
    244     /**
    245      * Subtype to tag an item as representing the current int value for a {@link #SUBTYPE_RANGE}.
    246      * Expected to be on an item of format {@link SliceItem#FORMAT_INT}.
    247      */
    248     public static final String SUBTYPE_VALUE = "value";
    249     /**
    250      * Subtype to indicate that this content has a toggle action associated with it. To indicate
    251      * that the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the
    252      * intent associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE}
    253      * which can be retrieved to see the new state of the toggle.
    254      */
    255     public static final String SUBTYPE_TOGGLE = "toggle";
    256     /**
    257      * Subtype to tag an item representing priority.
    258      * Expected to be on an item of format {@link SliceItem#FORMAT_INT}.
    259      */
    260     public static final String SUBTYPE_PRIORITY = "priority";
    261     /**
    262      * Subtype to tag an item to use as a content description.
    263      * Expected to be on an item of format {@link SliceItem#FORMAT_TEXT}.
    264      */
    265     public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description";
    266     /**
    267      * Subtype to tag an item as representing a time in milliseconds since midnight,
    268      * January 1, 1970 UTC.
    269      */
    270     public static final String SUBTYPE_MILLIS = "millis";
    271 
    272     private final SliceItem[] mItems;
    273     private final @SliceHint String[] mHints;
    274     private SliceSpec mSpec;
    275     private Uri mUri;
    276 
    277     Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec) {
    278         mHints = hints;
    279         mItems = items.toArray(new SliceItem[items.size()]);
    280         mUri = uri;
    281         mSpec = spec;
    282     }
    283 
    284     protected Slice(Parcel in) {
    285         mHints = in.readStringArray();
    286         int n = in.readInt();
    287         mItems = new SliceItem[n];
    288         for (int i = 0; i < n; i++) {
    289             mItems[i] = SliceItem.CREATOR.createFromParcel(in);
    290         }
    291         mUri = Uri.CREATOR.createFromParcel(in);
    292         mSpec = in.readTypedObject(SliceSpec.CREATOR);
    293     }
    294 
    295     /**
    296      * @return The spec for this slice
    297      */
    298     public @Nullable SliceSpec getSpec() {
    299         return mSpec;
    300     }
    301 
    302     /**
    303      * @return The Uri that this Slice represents.
    304      */
    305     public Uri getUri() {
    306         return mUri;
    307     }
    308 
    309     /**
    310      * @return All child {@link SliceItem}s that this Slice contains.
    311      */
    312     public List<SliceItem> getItems() {
    313         return Arrays.asList(mItems);
    314     }
    315 
    316     /**
    317      * @return All hints associated with this Slice.
    318      */
    319     public @SliceHint List<String> getHints() {
    320         return Arrays.asList(mHints);
    321     }
    322 
    323     @Override
    324     public void writeToParcel(Parcel dest, int flags) {
    325         dest.writeStringArray(mHints);
    326         dest.writeInt(mItems.length);
    327         for (int i = 0; i < mItems.length; i++) {
    328             mItems[i].writeToParcel(dest, flags);
    329         }
    330         mUri.writeToParcel(dest, 0);
    331         dest.writeTypedObject(mSpec, flags);
    332     }
    333 
    334     @Override
    335     public int describeContents() {
    336         return 0;
    337     }
    338 
    339     /**
    340      * @hide
    341      */
    342     public boolean hasHint(@SliceHint String hint) {
    343         return ArrayUtils.contains(mHints, hint);
    344     }
    345 
    346     /**
    347      * Returns whether the caller for this slice matters.
    348      * @see Builder#setCallerNeeded
    349      */
    350     public boolean isCallerNeeded() {
    351         return hasHint(HINT_CALLER_NEEDED);
    352     }
    353 
    354     /**
    355      * A Builder used to construct {@link Slice}s
    356      */
    357     public static class Builder {
    358 
    359         private final Uri mUri;
    360         private ArrayList<SliceItem> mItems = new ArrayList<>();
    361         private @SliceHint ArrayList<String> mHints = new ArrayList<>();
    362         private SliceSpec mSpec;
    363 
    364         /**
    365          * @deprecated TO BE REMOVED
    366          * @removed
    367          */
    368         @Deprecated
    369         public Builder(@NonNull Uri uri) {
    370             mUri = uri;
    371         }
    372 
    373         /**
    374          * Create a builder which will construct a {@link Slice} for the given Uri.
    375          * @param uri Uri to tag for this slice.
    376          * @param spec the spec for this slice.
    377          */
    378         public Builder(@NonNull Uri uri, SliceSpec spec) {
    379             mUri = uri;
    380             mSpec = spec;
    381         }
    382 
    383         /**
    384          * Create a builder for a {@link Slice} that is a sub-slice of the slice
    385          * being constructed by the provided builder.
    386          * @param parent The builder constructing the parent slice
    387          */
    388         public Builder(@NonNull Slice.Builder parent) {
    389             mUri = parent.mUri.buildUpon().appendPath("_gen")
    390                     .appendPath(String.valueOf(mItems.size())).build();
    391         }
    392 
    393         /**
    394          * Tells the system whether for this slice the return value of
    395          * {@link SliceProvider#onBindSlice(Uri, java.util.Set)} may be different depending on
    396          * {@link SliceProvider#getCallingPackage()} and should not be cached for multiple
    397          * apps.
    398          */
    399         public Builder setCallerNeeded(boolean callerNeeded) {
    400             if (callerNeeded) {
    401                 mHints.add(HINT_CALLER_NEEDED);
    402             } else {
    403                 mHints.remove(HINT_CALLER_NEEDED);
    404             }
    405             return this;
    406         }
    407 
    408         /**
    409          * Add hints to the Slice being constructed
    410          */
    411         public Builder addHints(@SliceHint List<String> hints) {
    412             mHints.addAll(hints);
    413             return this;
    414         }
    415 
    416         /**
    417          * @deprecated TO BE REMOVED
    418          * @removed
    419          */
    420         public Builder setSpec(SliceSpec spec) {
    421             mSpec = spec;
    422             return this;
    423         }
    424 
    425         /**
    426          * Add a sub-slice to the slice being constructed
    427          * @param subType Optional template-specific type information
    428          * @see {@link SliceItem#getSubType()}
    429          */
    430         public Builder addSubSlice(@NonNull Slice slice, @Nullable @SliceSubtype String subType) {
    431             Preconditions.checkNotNull(slice);
    432             mItems.add(new SliceItem(slice, SliceItem.FORMAT_SLICE, subType,
    433                     slice.getHints().toArray(new String[slice.getHints().size()])));
    434             return this;
    435         }
    436 
    437         /**
    438          * Add an action to the slice being constructed
    439          * @param subType Optional template-specific type information
    440          * @see {@link SliceItem#getSubType()}
    441          */
    442         public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s,
    443                 @Nullable @SliceSubtype String subType) {
    444             Preconditions.checkNotNull(action);
    445             Preconditions.checkNotNull(s);
    446             List<String> hints = s.getHints();
    447             s.mSpec = null;
    448             mItems.add(new SliceItem(action, s, SliceItem.FORMAT_ACTION, subType, hints.toArray(
    449                     new String[hints.size()])));
    450             return this;
    451         }
    452 
    453         /**
    454          * Add text to the slice being constructed
    455          * @param subType Optional template-specific type information
    456          * @see {@link SliceItem#getSubType()}
    457          */
    458         public Builder addText(CharSequence text, @Nullable @SliceSubtype String subType,
    459                 @SliceHint List<String> hints) {
    460             mItems.add(new SliceItem(text, SliceItem.FORMAT_TEXT, subType, hints));
    461             return this;
    462         }
    463 
    464         /**
    465          * Add an image to the slice being constructed
    466          * @param subType Optional template-specific type information
    467          * @see {@link SliceItem#getSubType()}
    468          */
    469         public Builder addIcon(Icon icon, @Nullable @SliceSubtype String subType,
    470                 @SliceHint List<String> hints) {
    471             Preconditions.checkNotNull(icon);
    472             mItems.add(new SliceItem(icon, SliceItem.FORMAT_IMAGE, subType, hints));
    473             return this;
    474         }
    475 
    476         /**
    477          * Add remote input to the slice being constructed
    478          * @param subType Optional template-specific type information
    479          * @see {@link SliceItem#getSubType()}
    480          */
    481         public Slice.Builder addRemoteInput(RemoteInput remoteInput,
    482                 @Nullable @SliceSubtype String subType,
    483                 @SliceHint List<String> hints) {
    484             Preconditions.checkNotNull(remoteInput);
    485             mItems.add(new SliceItem(remoteInput, SliceItem.FORMAT_REMOTE_INPUT,
    486                     subType, hints));
    487             return this;
    488         }
    489 
    490         /**
    491          * Add an integer to the slice being constructed
    492          * @param subType Optional template-specific type information
    493          * @see {@link SliceItem#getSubType()}
    494          */
    495         public Builder addInt(int value, @Nullable @SliceSubtype String subType,
    496                 @SliceHint List<String> hints) {
    497             mItems.add(new SliceItem(value, SliceItem.FORMAT_INT, subType, hints));
    498             return this;
    499         }
    500 
    501         /**
    502          * @deprecated TO BE REMOVED.
    503          * @removed
    504          */
    505         @Deprecated
    506         public Slice.Builder addTimestamp(long time, @Nullable @SliceSubtype String subType,
    507                 @SliceHint List<String> hints) {
    508             return addLong(time, subType, hints);
    509         }
    510 
    511         /**
    512          * Add a long to the slice being constructed
    513          * @param subType Optional template-specific type information
    514          * @see {@link SliceItem#getSubType()}
    515          */
    516         public Slice.Builder addLong(long value, @Nullable @SliceSubtype String subType,
    517                 @SliceHint List<String> hints) {
    518             mItems.add(new SliceItem(value, SliceItem.FORMAT_LONG, subType,
    519                     hints.toArray(new String[hints.size()])));
    520             return this;
    521         }
    522 
    523         /**
    524          * Add a bundle to the slice being constructed.
    525          * <p>Expected to be used for support library extension, should not be used for general
    526          * development
    527          * @param subType Optional template-specific type information
    528          * @see {@link SliceItem#getSubType()}
    529          */
    530         public Slice.Builder addBundle(Bundle bundle, @Nullable @SliceSubtype String subType,
    531                 @SliceHint List<String> hints) {
    532             Preconditions.checkNotNull(bundle);
    533             mItems.add(new SliceItem(bundle, SliceItem.FORMAT_BUNDLE, subType,
    534                     hints));
    535             return this;
    536         }
    537 
    538         /**
    539          * Construct the slice.
    540          */
    541         public Slice build() {
    542             return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
    543         }
    544     }
    545 
    546     public static final Creator<Slice> CREATOR = new Creator<Slice>() {
    547         @Override
    548         public Slice createFromParcel(Parcel in) {
    549             return new Slice(in);
    550         }
    551 
    552         @Override
    553         public Slice[] newArray(int size) {
    554             return new Slice[size];
    555         }
    556     };
    557 
    558     /**
    559      * @hide
    560      * @return A string representation of this slice.
    561      */
    562     public String toString() {
    563         return toString("");
    564     }
    565 
    566     private String toString(String indent) {
    567         StringBuilder sb = new StringBuilder();
    568         for (int i = 0; i < mItems.length; i++) {
    569             sb.append(indent);
    570             if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_SLICE)) {
    571                 sb.append("slice:\n");
    572                 sb.append(mItems[i].getSlice().toString(indent + "   "));
    573             } else if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_TEXT)) {
    574                 sb.append("text: ");
    575                 sb.append(mItems[i].getText());
    576                 sb.append("\n");
    577             } else {
    578                 sb.append(mItems[i].getFormat());
    579                 sb.append("\n");
    580             }
    581         }
    582         return sb.toString();
    583     }
    584 }
    585