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.SliceItem.FORMAT_ACTION; 20 import static android.app.slice.SliceItem.FORMAT_IMAGE; 21 import static android.app.slice.SliceItem.FORMAT_INT; 22 import static android.app.slice.SliceItem.FORMAT_LONG; 23 import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT; 24 import static android.app.slice.SliceItem.FORMAT_SLICE; 25 import static android.app.slice.SliceItem.FORMAT_TEXT; 26 27 import static androidx.slice.Slice.addHints; 28 29 import android.app.PendingIntent; 30 import android.app.RemoteInput; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.net.Uri; 34 import android.os.Bundle; 35 import android.os.Parcelable; 36 import android.text.TextUtils; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.RequiresApi; 40 import androidx.annotation.RestrictTo; 41 import androidx.annotation.RestrictTo.Scope; 42 import androidx.annotation.StringDef; 43 import androidx.core.graphics.drawable.IconCompat; 44 import androidx.core.util.Consumer; 45 import androidx.core.util.Pair; 46 47 import java.util.Arrays; 48 import java.util.List; 49 50 51 /** 52 * A SliceItem is a single unit in the tree structure of a {@link Slice}. 53 * <p> 54 * A SliceItem a piece of content and some hints about what that content 55 * means or how it should be displayed. The types of content can be: 56 * <li>{@link android.app.slice.SliceItem#FORMAT_SLICE}</li> 57 * <li>{@link android.app.slice.SliceItem#FORMAT_TEXT}</li> 58 * <li>{@link android.app.slice.SliceItem#FORMAT_IMAGE}</li> 59 * <li>{@link android.app.slice.SliceItem#FORMAT_ACTION}</li> 60 * <li>{@link android.app.slice.SliceItem#FORMAT_INT}</li> 61 * <li>{@link android.app.slice.SliceItem#FORMAT_LONG}</li> 62 * <p> 63 * The hints that a {@link SliceItem} are a set of strings which annotate 64 * the content. The hints that are guaranteed to be understood by the system 65 * are defined on {@link Slice}. 66 */ 67 public class SliceItem { 68 69 private static final String HINTS = "hints"; 70 private static final String FORMAT = "format"; 71 private static final String SUBTYPE = "subtype"; 72 private static final String OBJ = "obj"; 73 private static final String OBJ_2 = "obj_2"; 74 75 /** 76 * @hide 77 */ 78 @RestrictTo(Scope.LIBRARY) 79 @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_INT, 80 FORMAT_LONG, FORMAT_REMOTE_INPUT, FORMAT_LONG}) 81 public @interface SliceType { 82 } 83 84 /** 85 * @hide 86 */ 87 @RestrictTo(Scope.LIBRARY) 88 protected @Slice.SliceHint String[] mHints; 89 private final String mFormat; 90 private final String mSubType; 91 private final Object mObj; 92 93 /** 94 * @hide 95 */ 96 @RestrictTo(Scope.LIBRARY) SliceItem(Object obj, @SliceType String format, String subType, @Slice.SliceHint String[] hints)97 public SliceItem(Object obj, @SliceType String format, String subType, 98 @Slice.SliceHint String[] hints) { 99 mHints = hints; 100 mFormat = format; 101 mSubType = subType; 102 mObj = obj; 103 } 104 105 /** 106 * @hide 107 */ 108 @RestrictTo(Scope.LIBRARY) SliceItem(Object obj, @SliceType String format, String subType, @Slice.SliceHint List<String> hints)109 public SliceItem(Object obj, @SliceType String format, String subType, 110 @Slice.SliceHint List<String> hints) { 111 this (obj, format, subType, hints.toArray(new String[hints.size()])); 112 } 113 114 /** 115 * @hide 116 */ 117 @RestrictTo(Scope.LIBRARY) SliceItem(PendingIntent intent, Slice slice, String format, String subType, @Slice.SliceHint String[] hints)118 public SliceItem(PendingIntent intent, Slice slice, String format, String subType, 119 @Slice.SliceHint String[] hints) { 120 this(new Pair<Object, Slice>(intent, slice), format, subType, hints); 121 } 122 123 /** 124 * @hide 125 */ 126 @RestrictTo(Scope.LIBRARY) SliceItem(Consumer<Uri> action, Slice slice, String format, String subType, @Slice.SliceHint String[] hints)127 public SliceItem(Consumer<Uri> action, Slice slice, String format, String subType, 128 @Slice.SliceHint String[] hints) { 129 this(new Pair<Object, Slice>(action, slice), format, subType, hints); 130 } 131 132 /** 133 * Gets all hints associated with this SliceItem. 134 * 135 * @return Array of hints. 136 */ getHints()137 public @NonNull @Slice.SliceHint List<String> getHints() { 138 return Arrays.asList(mHints); 139 } 140 141 /** 142 * @hide 143 */ 144 @RestrictTo(Scope.LIBRARY) addHint(@lice.SliceHint String hint)145 public void addHint(@Slice.SliceHint String hint) { 146 mHints = ArrayUtils.appendElement(String.class, mHints, hint); 147 } 148 149 /** 150 * @hide 151 */ 152 @RestrictTo(Scope.LIBRARY) removeHint(String hint)153 public void removeHint(String hint) { 154 ArrayUtils.removeElement(String.class, mHints, hint); 155 } 156 157 /** 158 * Get the format of this SliceItem. 159 * <p> 160 * The format will be one of the following types supported by the platform: 161 * <li>{@link android.app.slice.SliceItem#FORMAT_SLICE}</li> 162 * <li>{@link android.app.slice.SliceItem#FORMAT_TEXT}</li> 163 * <li>{@link android.app.slice.SliceItem#FORMAT_IMAGE}</li> 164 * <li>{@link android.app.slice.SliceItem#FORMAT_ACTION}</li> 165 * <li>{@link android.app.slice.SliceItem#FORMAT_INT}</li> 166 * <li>{@link android.app.slice.SliceItem#FORMAT_LONG}</li> 167 * <li>{@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT}</li> 168 * @see #getSubType() () 169 */ getFormat()170 public @SliceType String getFormat() { 171 return mFormat; 172 } 173 174 /** 175 * Get the sub-type of this SliceItem. 176 * <p> 177 * Subtypes provide additional information about the type of this information beyond basic 178 * interpretations inferred by {@link #getFormat()}. For example a slice may contain 179 * many {@link android.app.slice.SliceItem#FORMAT_TEXT} items, but only some of them may be 180 * {@link android.app.slice.Slice#SUBTYPE_MESSAGE}. 181 * @see #getFormat() 182 */ getSubType()183 public String getSubType() { 184 return mSubType; 185 } 186 187 /** 188 * @return The text held by this {@link android.app.slice.SliceItem#FORMAT_TEXT} SliceItem 189 */ getText()190 public CharSequence getText() { 191 return (CharSequence) mObj; 192 } 193 194 /** 195 * @return The icon held by this {@link android.app.slice.SliceItem#FORMAT_IMAGE} SliceItem 196 */ getIcon()197 public IconCompat getIcon() { 198 return (IconCompat) mObj; 199 } 200 201 /** 202 * @return The pending intent held by this {@link android.app.slice.SliceItem#FORMAT_ACTION} 203 * SliceItem 204 */ getAction()205 public PendingIntent getAction() { 206 return (PendingIntent) ((Pair<Object, Slice>) mObj).first; 207 } 208 209 /** 210 * @hide 211 */ 212 @RestrictTo(Scope.LIBRARY_GROUP) fireAction(Context context, Intent i)213 public void fireAction(Context context, Intent i) throws PendingIntent.CanceledException { 214 Object action = ((Pair<Object, Slice>) mObj).first; 215 if (action instanceof PendingIntent) { 216 ((PendingIntent) action).send(context, 0, i, null, null); 217 } else { 218 ((Consumer<Uri>) action).accept(getSlice().getUri()); 219 } 220 } 221 222 /** 223 * @return The remote input held by this {@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT} 224 * SliceItem 225 * @hide 226 */ 227 @RequiresApi(20) 228 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) getRemoteInput()229 public RemoteInput getRemoteInput() { 230 return (RemoteInput) mObj; 231 } 232 233 /** 234 * @return The color held by this {@link android.app.slice.SliceItem#FORMAT_INT} SliceItem 235 */ getInt()236 public int getInt() { 237 return (Integer) mObj; 238 } 239 240 /** 241 * @return The slice held by this {@link android.app.slice.SliceItem#FORMAT_ACTION} or 242 * {@link android.app.slice.SliceItem#FORMAT_SLICE} SliceItem 243 */ getSlice()244 public Slice getSlice() { 245 if (FORMAT_ACTION.equals(getFormat())) { 246 return ((Pair<Object, Slice>) mObj).second; 247 } 248 return (Slice) mObj; 249 } 250 251 /** 252 * @return The long held by this {@link android.app.slice.SliceItem#FORMAT_LONG} 253 * SliceItem 254 */ getLong()255 public long getLong() { 256 return (Long) mObj; 257 } 258 259 /** 260 * @deprecated TO BE REMOVED 261 */ 262 @Deprecated getTimestamp()263 public long getTimestamp() { 264 return (Long) mObj; 265 } 266 267 /** 268 * @param hint The hint to check for 269 * @return true if this item contains the given hint 270 */ hasHint(@lice.SliceHint String hint)271 public boolean hasHint(@Slice.SliceHint String hint) { 272 return ArrayUtils.contains(mHints, hint); 273 } 274 275 /** 276 * @hide 277 */ 278 @RestrictTo(Scope.LIBRARY) SliceItem(Bundle in)279 public SliceItem(Bundle in) { 280 mHints = in.getStringArray(HINTS); 281 mFormat = in.getString(FORMAT); 282 mSubType = in.getString(SUBTYPE); 283 mObj = readObj(mFormat, in); 284 } 285 286 /** 287 * @hide 288 * @return 289 */ 290 @RestrictTo(Scope.LIBRARY) toBundle()291 public Bundle toBundle() { 292 Bundle b = new Bundle(); 293 b.putStringArray(HINTS, mHints); 294 b.putString(FORMAT, mFormat); 295 b.putString(SUBTYPE, mSubType); 296 writeObj(b, mObj, mFormat); 297 return b; 298 } 299 300 /** 301 * @hide 302 */ 303 @RestrictTo(Scope.LIBRARY) hasHints(@lice.SliceHint String[] hints)304 public boolean hasHints(@Slice.SliceHint String[] hints) { 305 if (hints == null) return true; 306 for (String hint : hints) { 307 if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) { 308 return false; 309 } 310 } 311 return true; 312 } 313 314 /** 315 * @hide 316 */ 317 @RestrictTo(Scope.LIBRARY) hasAnyHints(@lice.SliceHint String... hints)318 public boolean hasAnyHints(@Slice.SliceHint String... hints) { 319 if (hints == null) return false; 320 for (String hint : hints) { 321 if (ArrayUtils.contains(mHints, hint)) { 322 return true; 323 } 324 } 325 return false; 326 } 327 writeObj(Bundle dest, Object obj, String type)328 private void writeObj(Bundle dest, Object obj, String type) { 329 switch (type) { 330 case FORMAT_IMAGE: 331 dest.putBundle(OBJ, ((IconCompat) obj).toBundle()); 332 break; 333 case FORMAT_REMOTE_INPUT: 334 dest.putParcelable(OBJ, (Parcelable) obj); 335 break; 336 case FORMAT_SLICE: 337 dest.putParcelable(OBJ, ((Slice) obj).toBundle()); 338 break; 339 case FORMAT_ACTION: 340 dest.putParcelable(OBJ, (PendingIntent) ((Pair<Object, Slice>) obj).first); 341 dest.putBundle(OBJ_2, ((Pair<Object, Slice>) obj).second.toBundle()); 342 break; 343 case FORMAT_TEXT: 344 dest.putCharSequence(OBJ, (CharSequence) obj); 345 break; 346 case FORMAT_INT: 347 dest.putInt(OBJ, (Integer) mObj); 348 break; 349 case FORMAT_LONG: 350 dest.putLong(OBJ, (Long) mObj); 351 break; 352 } 353 } 354 readObj(String type, Bundle in)355 private static Object readObj(String type, Bundle in) { 356 switch (type) { 357 case FORMAT_IMAGE: 358 return IconCompat.createFromBundle(in.getBundle(OBJ)); 359 case FORMAT_REMOTE_INPUT: 360 return in.getParcelable(OBJ); 361 case FORMAT_SLICE: 362 return new Slice(in.getBundle(OBJ)); 363 case FORMAT_TEXT: 364 return in.getCharSequence(OBJ); 365 case FORMAT_ACTION: 366 return new Pair<>( 367 in.getParcelable(OBJ), 368 new Slice(in.getBundle(OBJ_2))); 369 case FORMAT_INT: 370 return in.getInt(OBJ); 371 case FORMAT_LONG: 372 return in.getLong(OBJ); 373 } 374 throw new RuntimeException("Unsupported type " + type); 375 } 376 377 /** 378 * @hide 379 */ 380 @RestrictTo(Scope.LIBRARY) typeToString(String format)381 public static String typeToString(String format) { 382 switch (format) { 383 case FORMAT_SLICE: 384 return "Slice"; 385 case FORMAT_TEXT: 386 return "Text"; 387 case FORMAT_IMAGE: 388 return "Image"; 389 case FORMAT_ACTION: 390 return "Action"; 391 case FORMAT_INT: 392 return "Int"; 393 case FORMAT_LONG: 394 return "Long"; 395 case FORMAT_REMOTE_INPUT: 396 return "RemoteInput"; 397 } 398 return "Unrecognized format: " + format; 399 } 400 401 /** 402 * @return A string representation of this slice item. 403 */ 404 @Override toString()405 public String toString() { 406 return toString(""); 407 } 408 409 /** 410 * @return A string representation of this slice item. 411 * @hide 412 */ 413 @RestrictTo(Scope.LIBRARY) toString(String indent)414 public String toString(String indent) { 415 StringBuilder sb = new StringBuilder(); 416 switch (getFormat()) { 417 case FORMAT_SLICE: 418 sb.append(getSlice().toString(indent)); 419 break; 420 case FORMAT_ACTION: 421 sb.append(indent).append(getAction()).append(",\n"); 422 sb.append(getSlice().toString(indent)); 423 break; 424 case FORMAT_TEXT: 425 sb.append(indent).append('"').append(getText()).append('"'); 426 break; 427 case FORMAT_IMAGE: 428 sb.append(indent).append(getIcon()); 429 break; 430 case FORMAT_INT: 431 sb.append(indent).append(getInt()); 432 break; 433 case FORMAT_LONG: 434 sb.append(indent).append(getLong()); 435 break; 436 default: 437 sb.append(indent).append(SliceItem.typeToString(getFormat())); 438 break; 439 } 440 if (!FORMAT_SLICE.equals(getFormat())) { 441 sb.append(' '); 442 addHints(sb, mHints); 443 } 444 sb.append(",\n"); 445 return sb.toString(); 446 } 447 } 448