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