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