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