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 * @deprecated Slice framework has been deprecated, it will not receive any updates from 45 * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a 46 * framework that sends displayable data from one app to another, consider using 47 * {@link android.app.appsearch.AppSearchManager}. 48 */ 49 @Deprecated 50 public final class Slice implements Parcelable { 51 52 /** 53 * @hide 54 */ 55 @StringDef(prefix = { "HINT_" }, value = { 56 HINT_TITLE, 57 HINT_LIST, 58 HINT_LIST_ITEM, 59 HINT_LARGE, 60 HINT_ACTIONS, 61 HINT_SELECTED, 62 HINT_NO_TINT, 63 HINT_SHORTCUT, 64 HINT_TOGGLE, 65 HINT_HORIZONTAL, 66 HINT_PARTIAL, 67 HINT_SEE_MORE, 68 HINT_KEYWORDS, 69 HINT_ERROR, 70 HINT_TTL, 71 HINT_LAST_UPDATED, 72 HINT_PERMISSION_REQUEST, 73 }) 74 @Retention(RetentionPolicy.SOURCE) 75 public @interface SliceHint {} 76 /** 77 * @hide 78 */ 79 @StringDef(prefix = { "SUBTYPE_" }, value = { 80 SUBTYPE_COLOR, 81 SUBTYPE_CONTENT_DESCRIPTION, 82 SUBTYPE_MAX, 83 SUBTYPE_MESSAGE, 84 SUBTYPE_PRIORITY, 85 SUBTYPE_RANGE, 86 SUBTYPE_SOURCE, 87 SUBTYPE_TOGGLE, 88 SUBTYPE_VALUE, 89 SUBTYPE_LAYOUT_DIRECTION, 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 * Subtype to indicate that this item indicates the layout direction for content 194 * in the slice. 195 * Expected to be an item of format {@link SliceItem#FORMAT_INT}. 196 */ 197 public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction"; 198 /** 199 * Key to retrieve an extra added to an intent when a control is changed. 200 */ 201 public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; 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 range. 225 * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE} containing 226 * a {@link #SUBTYPE_VALUE} and possibly a {@link #SUBTYPE_MAX}. 227 */ 228 public static final String SUBTYPE_RANGE = "range"; 229 /** 230 * Subtype to tag an item as representing the max int value for a {@link #SUBTYPE_RANGE}. 231 * Expected to be on an item of format {@link SliceItem#FORMAT_INT}. 232 */ 233 public static final String SUBTYPE_MAX = "max"; 234 /** 235 * Subtype to tag an item as representing the current int value for a {@link #SUBTYPE_RANGE}. 236 * Expected to be on an item of format {@link SliceItem#FORMAT_INT}. 237 */ 238 public static final String SUBTYPE_VALUE = "value"; 239 /** 240 * Subtype to indicate that this content has a toggle action associated with it. To indicate 241 * that the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the 242 * intent associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} 243 * which can be retrieved to see the new state of the toggle. 244 */ 245 public static final String SUBTYPE_TOGGLE = "toggle"; 246 /** 247 * Subtype to tag an item representing priority. 248 * Expected to be on an item of format {@link SliceItem#FORMAT_INT}. 249 */ 250 public static final String SUBTYPE_PRIORITY = "priority"; 251 /** 252 * Subtype to tag an item to use as a content description. 253 * Expected to be on an item of format {@link SliceItem#FORMAT_TEXT}. 254 */ 255 public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description"; 256 /** 257 * Subtype to tag an item as representing a time in milliseconds since midnight, 258 * January 1, 1970 UTC. 259 */ 260 public static final String SUBTYPE_MILLIS = "millis"; 261 262 private final SliceItem[] mItems; 263 private final @SliceHint String[] mHints; 264 private SliceSpec mSpec; 265 private Uri mUri; 266 Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec)267 Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec) { 268 mHints = hints; 269 mItems = items.toArray(new SliceItem[items.size()]); 270 mUri = uri; 271 mSpec = spec; 272 } 273 Slice(Parcel in)274 protected Slice(Parcel in) { 275 mHints = in.readStringArray(); 276 int n = in.readInt(); 277 mItems = new SliceItem[n]; 278 for (int i = 0; i < n; i++) { 279 mItems[i] = SliceItem.CREATOR.createFromParcel(in); 280 } 281 mUri = Uri.CREATOR.createFromParcel(in); 282 mSpec = in.readTypedObject(SliceSpec.CREATOR); 283 } 284 285 /** 286 * @return The spec for this slice 287 */ getSpec()288 public @Nullable SliceSpec getSpec() { 289 return mSpec; 290 } 291 292 /** 293 * @return The Uri that this Slice represents. 294 */ getUri()295 public Uri getUri() { 296 return mUri; 297 } 298 299 /** 300 * @return All child {@link SliceItem}s that this Slice contains. 301 */ getItems()302 public List<SliceItem> getItems() { 303 return Arrays.asList(mItems); 304 } 305 306 /** 307 * @return All hints associated with this Slice. 308 */ getHints()309 public @SliceHint List<String> getHints() { 310 return Arrays.asList(mHints); 311 } 312 313 @Override writeToParcel(Parcel dest, int flags)314 public void writeToParcel(Parcel dest, int flags) { 315 dest.writeStringArray(mHints); 316 dest.writeInt(mItems.length); 317 for (int i = 0; i < mItems.length; i++) { 318 mItems[i].writeToParcel(dest, flags); 319 } 320 mUri.writeToParcel(dest, 0); 321 dest.writeTypedObject(mSpec, flags); 322 } 323 324 @Override describeContents()325 public int describeContents() { 326 return 0; 327 } 328 329 /** 330 * @hide 331 */ hasHint(@liceHint String hint)332 public boolean hasHint(@SliceHint String hint) { 333 return ArrayUtils.contains(mHints, hint); 334 } 335 336 /** 337 * Returns whether the caller for this slice matters. 338 * @see Builder#setCallerNeeded 339 */ isCallerNeeded()340 public boolean isCallerNeeded() { 341 return hasHint(HINT_CALLER_NEEDED); 342 } 343 344 /** 345 * A Builder used to construct {@link Slice}s 346 * @deprecated Slice framework has been deprecated, it will not receive any updates from 347 * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a 348 * framework that sends displayable data from one app to another, consider using 349 * {@link android.app.appsearch.AppSearchManager}. 350 */ 351 @Deprecated 352 public static class Builder { 353 354 private final Uri mUri; 355 private ArrayList<SliceItem> mItems = new ArrayList<>(); 356 private @SliceHint ArrayList<String> mHints = new ArrayList<>(); 357 private SliceSpec mSpec; 358 359 /** 360 * Create a builder which will construct a {@link Slice} for the given Uri. 361 * @param uri Uri to tag for this slice. 362 * @param spec the spec for this slice. 363 */ Builder(@onNull Uri uri, SliceSpec spec)364 public Builder(@NonNull Uri uri, SliceSpec spec) { 365 mUri = uri; 366 mSpec = spec; 367 } 368 369 /** 370 * Create a builder for a {@link Slice} that is a sub-slice of the slice 371 * being constructed by the provided builder. 372 * @param parent The builder constructing the parent slice 373 */ Builder(@onNull Slice.Builder parent)374 public Builder(@NonNull Slice.Builder parent) { 375 mUri = parent.mUri.buildUpon().appendPath("_gen") 376 .appendPath(String.valueOf(mItems.size())).build(); 377 } 378 379 /** 380 * Tells the system whether for this slice the return value of 381 * {@link SliceProvider#onBindSlice(Uri, java.util.Set)} may be different depending on 382 * {@link SliceProvider#getCallingPackage()} and should not be cached for multiple 383 * apps. 384 */ setCallerNeeded(boolean callerNeeded)385 public Builder setCallerNeeded(boolean callerNeeded) { 386 if (callerNeeded) { 387 mHints.add(HINT_CALLER_NEEDED); 388 } else { 389 mHints.remove(HINT_CALLER_NEEDED); 390 } 391 return this; 392 } 393 394 /** 395 * Add hints to the Slice being constructed 396 */ addHints(@liceHint List<String> hints)397 public Builder addHints(@SliceHint List<String> hints) { 398 mHints.addAll(hints); 399 return this; 400 } 401 402 /** 403 * Add a sub-slice to the slice being constructed 404 * @param subType Optional template-specific type information 405 * @see SliceItem#getSubType() 406 */ addSubSlice(@onNull Slice slice, @Nullable @SliceSubtype String subType)407 public Builder addSubSlice(@NonNull Slice slice, @Nullable @SliceSubtype String subType) { 408 Objects.requireNonNull(slice); 409 mItems.add(new SliceItem(slice, SliceItem.FORMAT_SLICE, subType, 410 slice.getHints().toArray(new String[slice.getHints().size()]))); 411 return this; 412 } 413 414 /** 415 * Add an action to the slice being constructed 416 * @param subType Optional template-specific type information 417 * @see SliceItem#getSubType() 418 */ addAction(@onNull PendingIntent action, @NonNull Slice s, @Nullable @SliceSubtype String subType)419 public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s, 420 @Nullable @SliceSubtype String subType) { 421 Objects.requireNonNull(action); 422 Objects.requireNonNull(s); 423 List<String> hints = s.getHints(); 424 s.mSpec = null; 425 mItems.add(new SliceItem(action, s, SliceItem.FORMAT_ACTION, subType, hints.toArray( 426 new String[hints.size()]))); 427 return this; 428 } 429 430 /** 431 * Add text to the slice being constructed 432 * @param subType Optional template-specific type information 433 * @see SliceItem#getSubType() 434 */ addText(CharSequence text, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)435 public Builder addText(CharSequence text, @Nullable @SliceSubtype String subType, 436 @SliceHint List<String> hints) { 437 mItems.add(new SliceItem(text, SliceItem.FORMAT_TEXT, subType, hints)); 438 return this; 439 } 440 441 /** 442 * Add an image to the slice being constructed 443 * @param subType Optional template-specific type information 444 * @see SliceItem#getSubType() 445 */ addIcon(Icon icon, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)446 public Builder addIcon(Icon icon, @Nullable @SliceSubtype String subType, 447 @SliceHint List<String> hints) { 448 Objects.requireNonNull(icon); 449 mItems.add(new SliceItem(icon, SliceItem.FORMAT_IMAGE, subType, hints)); 450 return this; 451 } 452 453 /** 454 * Add remote input to the slice being constructed 455 * @param subType Optional template-specific type information 456 * @see SliceItem#getSubType() 457 */ addRemoteInput(RemoteInput remoteInput, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)458 public Slice.Builder addRemoteInput(RemoteInput remoteInput, 459 @Nullable @SliceSubtype String subType, 460 @SliceHint List<String> hints) { 461 Objects.requireNonNull(remoteInput); 462 mItems.add(new SliceItem(remoteInput, SliceItem.FORMAT_REMOTE_INPUT, 463 subType, hints)); 464 return this; 465 } 466 467 /** 468 * Add an integer to the slice being constructed 469 * @param subType Optional template-specific type information 470 * @see SliceItem#getSubType() 471 */ addInt(int value, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)472 public Builder addInt(int value, @Nullable @SliceSubtype String subType, 473 @SliceHint List<String> hints) { 474 mItems.add(new SliceItem(value, SliceItem.FORMAT_INT, subType, hints)); 475 return this; 476 } 477 478 /** 479 * Add a long to the slice being constructed 480 * @param subType Optional template-specific type information 481 * @see SliceItem#getSubType() 482 */ addLong(long value, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)483 public Slice.Builder addLong(long value, @Nullable @SliceSubtype String subType, 484 @SliceHint List<String> hints) { 485 mItems.add(new SliceItem(value, SliceItem.FORMAT_LONG, subType, 486 hints.toArray(new String[hints.size()]))); 487 return this; 488 } 489 490 /** 491 * Add a bundle to the slice being constructed. 492 * <p>Expected to be used for support library extension, should not be used for general 493 * development 494 * @param subType Optional template-specific type information 495 * @see SliceItem#getSubType() 496 */ addBundle(Bundle bundle, @Nullable @SliceSubtype String subType, @SliceHint List<String> hints)497 public Slice.Builder addBundle(Bundle bundle, @Nullable @SliceSubtype String subType, 498 @SliceHint List<String> hints) { 499 Objects.requireNonNull(bundle); 500 mItems.add(new SliceItem(bundle, SliceItem.FORMAT_BUNDLE, subType, 501 hints)); 502 return this; 503 } 504 505 /** 506 * Construct the slice. 507 */ build()508 public Slice build() { 509 return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec); 510 } 511 } 512 513 public static final @android.annotation.NonNull Creator<Slice> CREATOR = new Creator<Slice>() { 514 @Override 515 public Slice createFromParcel(Parcel in) { 516 return new Slice(in); 517 } 518 519 @Override 520 public Slice[] newArray(int size) { 521 return new Slice[size]; 522 } 523 }; 524 525 /** 526 * @hide 527 * @return A string representation of this slice. 528 */ toString()529 public String toString() { 530 return toString(""); 531 } 532 toString(String indent)533 private String toString(String indent) { 534 StringBuilder sb = new StringBuilder(); 535 for (int i = 0; i < mItems.length; i++) { 536 sb.append(indent); 537 if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_SLICE)) { 538 sb.append("slice:\n"); 539 sb.append(mItems[i].getSlice().toString(indent + " ")); 540 } else if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_TEXT)) { 541 sb.append("text: "); 542 sb.append(mItems[i].getText()); 543 sb.append("\n"); 544 } else { 545 sb.append(mItems[i].getFormat()); 546 sb.append("\n"); 547 } 548 } 549 return sb.toString(); 550 } 551 } 552