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 com.android.tv.twopanelsettings.slices.compat; 18 19 import static android.app.slice.Slice.HINT_ACTIONS; 20 import static android.app.slice.Slice.HINT_ERROR; 21 import static android.app.slice.Slice.HINT_HORIZONTAL; 22 import static android.app.slice.Slice.HINT_KEYWORDS; 23 import static android.app.slice.Slice.HINT_LARGE; 24 import static android.app.slice.Slice.HINT_LAST_UPDATED; 25 import static android.app.slice.Slice.HINT_LIST; 26 import static android.app.slice.Slice.HINT_LIST_ITEM; 27 import static android.app.slice.Slice.HINT_NO_TINT; 28 import static android.app.slice.Slice.HINT_PARTIAL; 29 import static android.app.slice.Slice.HINT_PERMISSION_REQUEST; 30 import static android.app.slice.Slice.HINT_SEE_MORE; 31 import static android.app.slice.Slice.HINT_SELECTED; 32 import static android.app.slice.Slice.HINT_SHORTCUT; 33 import static android.app.slice.Slice.HINT_SUMMARY; 34 import static android.app.slice.Slice.HINT_TITLE; 35 import static android.app.slice.Slice.HINT_TTL; 36 import static android.app.slice.SliceItem.FORMAT_ACTION; 37 import static android.app.slice.SliceItem.FORMAT_IMAGE; 38 import static android.app.slice.SliceItem.FORMAT_INT; 39 import static android.app.slice.SliceItem.FORMAT_LONG; 40 import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT; 41 import static android.app.slice.SliceItem.FORMAT_SLICE; 42 import static android.app.slice.SliceItem.FORMAT_TEXT; 43 import static com.android.tv.twopanelsettings.slices.compat.SliceConvert.unwrap; 44 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_ACTIVITY; 45 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_CACHED; 46 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_END_OF_SECTION; 47 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_OVERLAY; 48 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_RAW; 49 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_SELECTION_OPTION; 50 import static com.android.tv.twopanelsettings.slices.compat.core.SliceHints.HINT_SHOW_LABEL; 51 52 import android.app.RemoteInput; 53 import android.content.Context; 54 import android.graphics.drawable.Icon; 55 import android.net.Uri; 56 import android.os.Bundle; 57 import android.os.Parcelable; 58 import androidx.annotation.NonNull; 59 import androidx.annotation.Nullable; 60 import androidx.annotation.StringDef; 61 import androidx.core.graphics.drawable.IconCompat; 62 import androidx.core.util.Preconditions; 63 import com.android.tv.twopanelsettings.slices.base.SliceManager; 64 import java.lang.annotation.Retention; 65 import java.lang.annotation.RetentionPolicy; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.List; 69 import java.util.Set; 70 71 /** 72 * A slice is a piece of app content and actions that can be surfaced outside of the app. A slice is 73 * identified by a Uri and served via a {@link SliceProvider}. 74 * 75 * <p>Slices are constructed using {@link 76 * com.android.tv.twopanelsettings.slices.compat.builders.TemplateSliceBuilder}s in a tree structure 77 * that provides the OS some information about how the content should be displayed. 78 * 79 * <p>Slice framework has been deprecated, it will not receive any updates moving forward. If you 80 * are looking for a framework that handles communication across apps, consider using {@link 81 * android.app.appsearch.AppSearchManager}. 82 */ 83 // @Deprecated // Supported for TV 84 public final class Slice { 85 86 /** Key to retrieve an extra added to an intent when an item in a selection is selected. */ 87 public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION"; 88 89 /** 90 * Subtype to tag an item as representing the progress bar mode for a {@link 91 * android.app.slice.Slice#SUBTYPE_RANGE} 92 */ 93 // @RestrictTo(Scope.LIBRARY_GROUP) 94 public static final String SUBTYPE_RANGE_MODE = "range_mode"; 95 96 private static final String HINTS = "hints"; 97 private static final String ITEMS = "items"; 98 private static final String URI = "uri"; 99 private static final String SPEC_TYPE = "type"; 100 private static final String SPEC_REVISION = "revision"; 101 102 static final String[] NO_HINTS = new String[0]; 103 static final SliceItem[] NO_ITEMS = new SliceItem[0]; 104 105 /** */ 106 // @RestrictTo(Scope.LIBRARY) 107 @StringDef({ 108 HINT_TITLE, 109 HINT_LIST, 110 HINT_LIST_ITEM, 111 HINT_LARGE, 112 HINT_ACTIONS, 113 HINT_SELECTED, 114 HINT_HORIZONTAL, 115 HINT_NO_TINT, 116 HINT_PARTIAL, 117 HINT_SUMMARY, 118 HINT_SEE_MORE, 119 HINT_SHORTCUT, 120 HINT_KEYWORDS, 121 HINT_TTL, 122 HINT_LAST_UPDATED, 123 HINT_PERMISSION_REQUEST, 124 HINT_ERROR, 125 HINT_ACTIVITY, 126 HINT_CACHED, 127 HINT_END_OF_SECTION, 128 HINT_SELECTION_OPTION, 129 HINT_RAW, 130 HINT_OVERLAY, 131 HINT_SHOW_LABEL 132 }) 133 @Retention(RetentionPolicy.SOURCE) 134 public @interface SliceHint {} 135 136 SliceSpec mSpec = null; 137 138 SliceItem[] mItems = NO_ITEMS; 139 140 @SliceHint 141 String[] mHints = NO_HINTS; 142 143 String mUri = null; 144 145 /** */ 146 // @RestrictTo(Scope.LIBRARY) Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec)147 Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec) { 148 mHints = hints; 149 mItems = items.toArray(new SliceItem[items.size()]); 150 mUri = uri.toString(); 151 mSpec = spec; 152 } 153 154 /** */ 155 // @RestrictTo(Scope.LIBRARY) 156 @SuppressWarnings("deprecation") Slice(@onNull Bundle in)157 public Slice(@NonNull Bundle in) { 158 mHints = in.getStringArray(HINTS); 159 Parcelable[] items = in.getParcelableArray(ITEMS); 160 mItems = new SliceItem[items.length]; 161 for (int i = 0; i < mItems.length; i++) { 162 if (items[i] instanceof Bundle) { 163 mItems[i] = new SliceItem((Bundle) items[i]); 164 } 165 } 166 mUri = in.getParcelable(URI).toString(); 167 mSpec = 168 in.containsKey(SPEC_TYPE) 169 ? new SliceSpec(in.getString(SPEC_TYPE), in.getInt(SPEC_REVISION)) 170 : null; 171 } 172 173 /** */ 174 // @RestrictTo(Scope.LIBRARY) toBundle()175 public @NonNull Bundle toBundle() { 176 Bundle b = new Bundle(); 177 b.putStringArray(HINTS, mHints); 178 Parcelable[] p = new Parcelable[mItems.length]; 179 for (int i = 0; i < mItems.length; i++) { 180 p[i] = mItems[i].toBundle(); 181 } 182 b.putParcelableArray(ITEMS, p); 183 b.putParcelable(URI, Uri.parse(mUri)); 184 if (mSpec != null) { 185 b.putString(SPEC_TYPE, mSpec.getType()); 186 b.putInt(SPEC_REVISION, mSpec.getRevision()); 187 } 188 return b; 189 } 190 191 /** 192 * @return The spec for this slice 193 */ 194 // @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) getSpec()195 public @Nullable SliceSpec getSpec() { 196 return mSpec; 197 } 198 199 /** 200 * @return The Uri that this Slice represents. 201 */ getUri()202 public @NonNull Uri getUri() { 203 return Uri.parse(mUri); 204 } 205 206 /** 207 * @return All child {@link SliceItem}s that this Slice contains. 208 */ getItems()209 public @NonNull List<SliceItem> getItems() { 210 return Arrays.asList(mItems); 211 } 212 213 /** 214 * @return 215 */ 216 // @RestrictTo(LIBRARY) getItemArray()217 public @NonNull SliceItem[] getItemArray() { 218 return mItems; 219 } 220 221 /** 222 * @return All hints associated with this Slice. 223 */ getHints()224 public @NonNull @SliceHint List<String> getHints() { 225 return Arrays.asList(mHints); 226 } 227 228 /** */ 229 // @RestrictTo(LIBRARY) getHintArray()230 public @NonNull @SliceHint String[] getHintArray() { 231 return mHints; 232 } 233 234 /** */ 235 // @RestrictTo(Scope.LIBRARY_GROUP_PREFIX) hasHint(@onNull @liceHint String hint)236 public boolean hasHint(@NonNull @SliceHint String hint) { 237 return ArrayUtils.contains(mHints, hint); 238 } 239 240 /** A Builder used to construct {@link Slice}s */ 241 // @RestrictTo(Scope.LIBRARY_GROUP_PREFIX) 242 public static class Builder { 243 244 private final Uri mUri; 245 private ArrayList<SliceItem> mItems = new ArrayList<>(); 246 private @SliceHint ArrayList<String> mHints = new ArrayList<>(); 247 private SliceSpec mSpec; 248 private int mChildId; 249 250 /** 251 * Create a builder which will construct a {@link Slice} for the Given Uri. 252 * 253 * @param uri Uri to tag for this slice. 254 */ Builder(@onNull Uri uri)255 public Builder(@NonNull Uri uri) { 256 mUri = uri; 257 } 258 259 /** 260 * Create a builder for a {@link Slice} that is a sub-slice of the slice being constructed by 261 * the provided builder. 262 * 263 * @param parent The builder constructing the parent slice 264 */ Builder(@onNull Builder parent)265 public Builder(@NonNull Builder parent) { 266 mUri = parent.getChildUri(); 267 } 268 getChildUri()269 private Uri getChildUri() { 270 return mUri.buildUpon().appendPath("_gen").appendPath(String.valueOf(mChildId++)).build(); 271 } 272 273 /** Add the spec for this slice. */ 274 // @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) setSpec(@ullable SliceSpec spec)275 public @NonNull Builder setSpec(@Nullable SliceSpec spec) { 276 mSpec = spec; 277 return this; 278 } 279 280 /** Add hints to the Slice being constructed */ addHints(@onNull @liceHint String... hints)281 public @NonNull Builder addHints(@NonNull @SliceHint String... hints) { 282 mHints.addAll(Arrays.asList(hints)); 283 return this; 284 } 285 286 /** Add hints to the Slice being constructed */ addHints(@onNull @liceHint List<String> hints)287 public @NonNull Builder addHints(@NonNull @SliceHint List<String> hints) { 288 return addHints(hints.toArray(new String[hints.size()])); 289 } 290 291 /** Add a sub-slice to the slice being constructed */ addSubSlice(@onNull Slice slice)292 public @NonNull Builder addSubSlice(@NonNull Slice slice) { 293 Preconditions.checkNotNull(slice); 294 return addSubSlice(slice, null); 295 } 296 297 /** 298 * Add a sub-slice to the slice being constructed 299 * 300 * @param subType Optional template-specific type information 301 * @see SliceItem#getSubType() 302 */ addSubSlice(@onNull Slice slice, @Nullable String subType)303 public @NonNull Builder addSubSlice(@NonNull Slice slice, @Nullable String subType) { 304 Preconditions.checkNotNull(slice); 305 mItems.add(new SliceItem(slice, FORMAT_SLICE, subType, slice.getHintArray())); 306 return this; 307 } 308 309 /** 310 * Add an action to the slice being constructed 311 * 312 * @param subType Optional template-specific type information 313 * @see SliceItem#getSubType() 314 */ addAction( @onNull Parcelable action, @NonNull Slice s, @Nullable String subType)315 public @NonNull Builder addAction( 316 @NonNull Parcelable action, @NonNull Slice s, @Nullable String subType) { 317 Preconditions.checkNotNull(action); 318 Preconditions.checkNotNull(s); 319 @SliceHint String[] hints = s.getHintArray(); 320 mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, hints)); 321 return this; 322 } 323 324 /** 325 * Add an action to the slice being constructed 326 * 327 * @param subType Optional template-specific type information 328 * @param action Callback to be triggered when a pending intent would normally be fired. 329 * @see SliceItem#getSubType() 330 */ addAction( @onNull Slice s, @Nullable String subType, @NonNull SliceItem.ActionHandler action)331 public @NonNull Builder addAction( 332 @NonNull Slice s, @Nullable String subType, @NonNull SliceItem.ActionHandler action) { 333 Preconditions.checkNotNull(s); 334 @SliceHint String[] hints = s.getHintArray(); 335 mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, hints)); 336 return this; 337 } 338 339 /** 340 * Add text to the slice being constructed 341 * 342 * @param subType Optional template-specific type information 343 * @see SliceItem#getSubType() 344 */ addText( @ullable CharSequence text, @Nullable String subType, @NonNull @SliceHint String... hints)345 public @NonNull Builder addText( 346 @Nullable CharSequence text, 347 @Nullable String subType, 348 @NonNull @SliceHint String... hints) { 349 mItems.add(new SliceItem(text, FORMAT_TEXT, subType, hints)); 350 return this; 351 } 352 353 /** 354 * Add text to the slice being constructed 355 * 356 * @param subType Optional template-specific type information 357 * @see SliceItem#getSubType() 358 */ addText( @ullable CharSequence text, @Nullable String subType, @NonNull @SliceHint List<String> hints)359 public @NonNull Builder addText( 360 @Nullable CharSequence text, 361 @Nullable String subType, 362 @NonNull @SliceHint List<String> hints) { 363 return addText(text, subType, hints.toArray(new String[hints.size()])); 364 } 365 366 /** 367 * Add an image to the slice being constructed 368 * 369 * @param subType Optional template-specific type information 370 * @see SliceItem#getSubType() 371 */ addIcon( @onNull IconCompat icon, @Nullable String subType, @NonNull @SliceHint String... hints)372 public @NonNull Builder addIcon( 373 @NonNull IconCompat icon, @Nullable String subType, @NonNull @SliceHint String... hints) { 374 Preconditions.checkNotNull(icon); 375 if (isValidIcon(icon)) { 376 mItems.add(new SliceItem(icon, FORMAT_IMAGE, subType, hints)); 377 } 378 return this; 379 } 380 381 /** 382 * Add an image to the slice being constructed 383 * 384 * @param subType Optional template-specific type information 385 * @see SliceItem#getSubType() 386 */ addIcon( @onNull IconCompat icon, @Nullable String subType, @NonNull @SliceHint List<String> hints)387 public @NonNull Builder addIcon( 388 @NonNull IconCompat icon, 389 @Nullable String subType, 390 @NonNull @SliceHint List<String> hints) { 391 Preconditions.checkNotNull(icon); 392 if (isValidIcon(icon)) { 393 return addIcon(icon, subType, hints.toArray(new String[hints.size()])); 394 } 395 return this; 396 } 397 398 /** 399 * Add remote input to the slice being constructed 400 * 401 * @param subType Optional template-specific type information 402 * @see SliceItem#getSubType() 403 */ 404 // @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) addRemoteInput( @onNull RemoteInput remoteInput, @Nullable String subType, @NonNull @SliceHint List<String> hints)405 public @NonNull Builder addRemoteInput( 406 @NonNull RemoteInput remoteInput, 407 @Nullable String subType, 408 @NonNull @SliceHint List<String> hints) { 409 Preconditions.checkNotNull(remoteInput); 410 return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()])); 411 } 412 413 /** 414 * Add remote input to the slice being constructed 415 * 416 * @param subType Optional template-specific type information 417 * @see SliceItem#getSubType() 418 */ 419 // @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) addRemoteInput( @onNull RemoteInput remoteInput, @Nullable String subType, @NonNull @SliceHint String... hints)420 public @NonNull Builder addRemoteInput( 421 @NonNull RemoteInput remoteInput, 422 @Nullable String subType, 423 @NonNull @SliceHint String... hints) { 424 Preconditions.checkNotNull(remoteInput); 425 mItems.add(new SliceItem(remoteInput, FORMAT_REMOTE_INPUT, subType, hints)); 426 return this; 427 } 428 429 /** 430 * Add a int to the slice being constructed 431 * 432 * @param subType Optional template-specific type information 433 * @see SliceItem#getSubType() 434 */ addInt( int value, @Nullable String subType, @NonNull @SliceHint String... hints)435 public @NonNull Builder addInt( 436 int value, @Nullable String subType, @NonNull @SliceHint String... hints) { 437 mItems.add(new SliceItem(value, FORMAT_INT, subType, hints)); 438 return this; 439 } 440 441 /** 442 * Add a int to the slice being constructed 443 * 444 * @param subType Optional template-specific type information 445 * @see SliceItem#getSubType() 446 */ addInt( int value, @Nullable String subType, @NonNull @SliceHint List<String> hints)447 public @NonNull Builder addInt( 448 int value, @Nullable String subType, @NonNull @SliceHint List<String> hints) { 449 return addInt(value, subType, hints.toArray(new String[hints.size()])); 450 } 451 452 /** 453 * Add a long to the slice being constructed 454 * 455 * @param subType Optional template-specific type information 456 * @see SliceItem#getSubType() 457 */ addLong( long time, @Nullable String subType, @NonNull @SliceHint String... hints)458 public @NonNull Builder addLong( 459 long time, @Nullable String subType, @NonNull @SliceHint String... hints) { 460 mItems.add(new SliceItem(time, FORMAT_LONG, subType, hints)); 461 return this; 462 } 463 464 /** 465 * Add a long to the slice being constructed 466 * 467 * @param subType Optional template-specific type information 468 * @see SliceItem#getSubType() 469 */ addLong( long time, @Nullable String subType, @NonNull @SliceHint List<String> hints)470 public @NonNull Builder addLong( 471 long time, @Nullable String subType, @NonNull @SliceHint List<String> hints) { 472 return addLong(time, subType, hints.toArray(new String[hints.size()])); 473 } 474 475 /** 476 * Add a timestamp to the slice being constructed 477 * 478 * @param subType Optional template-specific type information 479 * @see SliceItem#getSubType() TO BE REMOVED 480 */ 481 // @Deprecated // Supported for TV addTimestamp(long time, @Nullable String subType, @SliceHint String... hints)482 public Builder addTimestamp(long time, @Nullable String subType, @SliceHint String... hints) { 483 mItems.add(new SliceItem(time, FORMAT_LONG, subType, hints)); 484 return this; 485 } 486 487 /** 488 * Add a timestamp to the slice being constructed 489 * 490 * @param subType Optional template-specific type information 491 * @see SliceItem#getSubType() 492 */ addTimestamp( long time, @Nullable String subType, @NonNull @SliceHint List<String> hints)493 public @NonNull Builder addTimestamp( 494 long time, @Nullable String subType, @NonNull @SliceHint List<String> hints) { 495 return addTimestamp(time, subType, hints.toArray(new String[hints.size()])); 496 } 497 498 /** Add a SliceItem to the slice being constructed. */ 499 // @RestrictTo(Scope.LIBRARY_GROUP) addItem(@onNull SliceItem item)500 public @NonNull Builder addItem(@NonNull SliceItem item) { 501 mItems.add(item); 502 return this; 503 } 504 505 /** Construct the slice. */ build()506 public @NonNull Slice build() { 507 return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec); 508 } 509 } 510 511 /** 512 * @return A string representation of this slice. 513 */ 514 @NonNull 515 @Override toString()516 public String toString() { 517 return toString(""); 518 } 519 520 /** 521 * @return A string representation of this slice. 522 */ 523 @NonNull 524 // @RestrictTo(Scope.LIBRARY) toString(@onNull String indent)525 public String toString(@NonNull String indent) { 526 StringBuilder sb = new StringBuilder(); 527 sb.append(indent); 528 sb.append("Slice "); 529 if (mHints.length > 0) { 530 appendHints(sb, mHints); 531 sb.append(' '); 532 } 533 sb.append('['); 534 sb.append(mUri); 535 sb.append("] {\n"); 536 final String nextIndent = indent + " "; 537 for (int i = 0; i < mItems.length; i++) { 538 SliceItem item = mItems[i]; 539 sb.append(item.toString(nextIndent)); 540 } 541 sb.append(indent); 542 sb.append('}'); 543 return sb.toString(); 544 } 545 546 /** */ 547 // @RestrictTo(Scope.LIBRARY) appendHints(@onNull StringBuilder sb, @Nullable String[] hints)548 public static void appendHints(@NonNull StringBuilder sb, @Nullable String[] hints) { 549 if (hints == null || hints.length == 0) { 550 return; 551 } 552 553 sb.append('('); 554 int end = hints.length - 1; 555 for (int i = 0; i < end; i++) { 556 sb.append(hints[i]); 557 sb.append(", "); 558 } 559 sb.append(hints[end]); 560 sb.append(")"); 561 } 562 563 /** 564 * Turns a slice Uri into slice content. 565 * 566 * @param context Context to be used. 567 * @param uri The URI to a slice provider 568 * @return The Slice provided by the app or null if none is given. 569 * @see Slice 570 */ 571 // @RestrictTo(Scope.LIBRARY_GROUP_PREFIX) 572 @Nullable bindSlice( @onNull Context context, @NonNull Uri uri, @Nullable Set<SliceSpec> supportedSpecs)573 public static Slice bindSlice( 574 @NonNull Context context, @NonNull Uri uri, @Nullable Set<SliceSpec> supportedSpecs) { 575 return SliceViewManagerWrapper.bindSlice( 576 context, SliceManager.from(context), uri, unwrap(supportedSpecs)); 577 } 578 579 /** */ 580 // @RestrictTo(LIBRARY) isValidIcon(IconCompat icon)581 static boolean isValidIcon(IconCompat icon) { 582 if (icon == null) { 583 return false; 584 } 585 if (icon.mType == Icon.TYPE_RESOURCE && icon.getResId() == 0) { 586 throw new IllegalArgumentException( 587 "Failed to add icon, invalid resource id: " + icon.getResId()); 588 } 589 return true; 590 } 591 } 592