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