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