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 Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec)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 Slice(Parcel in)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 */ getSpec()298 public @Nullable SliceSpec getSpec() { 299 return mSpec; 300 } 301 302 /** 303 * @return The Uri that this Slice represents. 304 */ getUri()305 public Uri getUri() { 306 return mUri; 307 } 308 309 /** 310 * @return All child {@link SliceItem}s that this Slice contains. 311 */ getItems()312 public List<SliceItem> getItems() { 313 return Arrays.asList(mItems); 314 } 315 316 /** 317 * @return All hints associated with this Slice. 318 */ getHints()319 public @SliceHint List<String> getHints() { 320 return Arrays.asList(mHints); 321 } 322 323 @Override writeToParcel(Parcel dest, int flags)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 describeContents()335 public int describeContents() { 336 return 0; 337 } 338 339 /** 340 * @hide 341 */ hasHint(@liceHint String hint)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 */ isCallerNeeded()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 Builder(@onNull Uri uri)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 */ Builder(@onNull Uri uri, SliceSpec spec)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 */ Builder(@onNull Slice.Builder parent)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 */ setCallerNeeded(boolean callerNeeded)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 */ addHints(@liceHint List<String> hints)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 */ setSpec(SliceSpec spec)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 SliceItem#getSubType() 429 */ addSubSlice(@onNull Slice slice, @Nullable @SliceSubtype String subType)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 SliceItem#getSubType() 441 */ addAction(@onNull PendingIntent action, @NonNull Slice s, @Nullable @SliceSubtype String subType)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 SliceItem#getSubType() 457 */ addText(CharSequence text, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)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 SliceItem#getSubType() 468 */ addIcon(Icon icon, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)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 SliceItem#getSubType() 480 */ addRemoteInput(RemoteInput remoteInput, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)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 SliceItem#getSubType() 494 */ addInt(int value, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)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 addTimestamp(long time, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)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 SliceItem#getSubType() 515 */ addLong(long value, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)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 SliceItem#getSubType() 529 */ addBundle(Bundle bundle, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)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 */ build()541 public Slice build() { 542 return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec); 543 } 544 } 545 546 public static final @android.annotation.NonNull 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 */ toString()562 public String toString() { 563 return toString(""); 564 } 565 toString(String indent)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