1 /* 2 * Copyright 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_PARTIAL; 21 import static android.app.slice.Slice.HINT_SHORTCUT; 22 import static android.app.slice.SliceItem.FORMAT_ACTION; 23 import static android.app.slice.SliceItem.FORMAT_IMAGE; 24 import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT; 25 import static android.app.slice.SliceItem.FORMAT_SLICE; 26 import static android.app.slice.SliceItem.FORMAT_TEXT; 27 28 import static androidx.slice.SliceMetadata.LOADED_ALL; 29 import static androidx.slice.SliceMetadata.LOADED_NONE; 30 import static androidx.slice.SliceMetadata.LOADED_PARTIAL; 31 import static androidx.slice.core.SliceHints.HINT_KEYWORDS; 32 33 import static java.lang.annotation.RetentionPolicy.SOURCE; 34 35 import android.content.Context; 36 import android.net.Uri; 37 import android.text.TextUtils; 38 39 import androidx.annotation.IntDef; 40 import androidx.annotation.NonNull; 41 import androidx.annotation.Nullable; 42 import androidx.annotation.RestrictTo; 43 import androidx.slice.core.SliceQuery; 44 45 import java.io.IOException; 46 import java.io.InputStream; 47 import java.io.OutputStream; 48 import java.lang.annotation.Retention; 49 import java.util.ArrayList; 50 import java.util.List; 51 52 /** 53 * Utilities for dealing with slices. 54 */ 55 public class SliceUtils { 56 SliceUtils()57 private SliceUtils() { 58 } 59 60 /** 61 * Serialize a slice to an OutputStream. 62 * <p> 63 * The slice can later be read into slice form again with {@link #parseSlice}. 64 * Some slice types cannot be serialized, their handling is controlled by 65 * {@link SerializeOptions}. 66 * 67 * @param s The slice to serialize. 68 * @param context Context used to load any resources in the slice. 69 * @param output The output of the serialization. 70 * @param encoding The encoding to use for serialization. 71 * @param options Options defining how to handle non-serializable items. 72 * @throws IllegalArgumentException if the slice cannot be serialized using the given options. 73 */ serializeSlice(@onNull Slice s, @NonNull Context context, @NonNull OutputStream output, @NonNull String encoding, @NonNull SerializeOptions options)74 public static void serializeSlice(@NonNull Slice s, @NonNull Context context, 75 @NonNull OutputStream output, @NonNull String encoding, 76 @NonNull SerializeOptions options) throws IOException, IllegalArgumentException { 77 SliceXml.serializeSlice(s, context, output, encoding, options); 78 } 79 80 /** 81 * Parse a slice that has been previously serialized. 82 * <p> 83 * Parses a slice that was serialized with {@link #serializeSlice}. 84 * <p> 85 * Note: Slices returned by this cannot be passed to {@link SliceConvert#unwrap(Slice)}. 86 * 87 * @param input The input stream to read from. 88 * @param encoding The encoding to read as. 89 * @param listener Listener used to handle actions when reconstructing the slice. 90 * @throws SliceParseException if the InputStream cannot be parsed. 91 */ parseSlice(@onNull Context context, @NonNull InputStream input, @NonNull String encoding, @NonNull SliceActionListener listener)92 public static @NonNull Slice parseSlice(@NonNull Context context, @NonNull InputStream input, 93 @NonNull String encoding, @NonNull SliceActionListener listener) 94 throws IOException, SliceParseException { 95 return SliceXml.parseSlice(context, input, encoding, listener); 96 } 97 98 /** 99 * Holds options for how to handle SliceItems that cannot be serialized. 100 */ 101 public static class SerializeOptions { 102 /** 103 * Constant indicating that the an {@link IllegalArgumentException} should be thrown 104 * when this format is encountered. 105 */ 106 public static final int MODE_THROW = 0; 107 /** 108 * Constant indicating that the SliceItem should be removed when this format is encountered. 109 */ 110 public static final int MODE_REMOVE = 1; 111 /** 112 * Constant indicating that the SliceItem should be serialized as much as possible. 113 * <p> 114 * For images this means they will be attempted to be serialized. For actions, the 115 * action will be removed but the content of the action will be serialized. The action 116 * may be triggered later on a de-serialized slice by binding the slice again and activating 117 * a pending-intent at the same location as the serialized action. 118 */ 119 public static final int MODE_CONVERT = 2; 120 121 @IntDef({MODE_THROW, MODE_REMOVE, MODE_CONVERT}) 122 @Retention(SOURCE) 123 @interface FormatMode { 124 } 125 126 private int mActionMode = MODE_THROW; 127 private int mImageMode = MODE_THROW; 128 private int mMaxWidth = 1000; 129 private int mMaxHeight = 1000; 130 131 /** 132 * @hide 133 */ 134 @RestrictTo(RestrictTo.Scope.LIBRARY) checkThrow(String format)135 public void checkThrow(String format) { 136 switch (format) { 137 case FORMAT_ACTION: 138 case FORMAT_REMOTE_INPUT: 139 if (mActionMode != MODE_THROW) return; 140 break; 141 case FORMAT_IMAGE: 142 if (mImageMode != MODE_THROW) return; 143 break; 144 default: 145 return; 146 } 147 throw new IllegalArgumentException(format + " cannot be serialized"); 148 } 149 150 /** 151 * @hide 152 */ 153 @RestrictTo(RestrictTo.Scope.LIBRARY) getActionMode()154 public @FormatMode int getActionMode() { 155 return mActionMode; 156 } 157 158 /** 159 * @hide 160 */ 161 @RestrictTo(RestrictTo.Scope.LIBRARY) getImageMode()162 public @FormatMode int getImageMode() { 163 return mImageMode; 164 } 165 166 /** 167 * @hide 168 */ 169 @RestrictTo(RestrictTo.Scope.LIBRARY) getMaxWidth()170 public int getMaxWidth() { 171 return mMaxWidth; 172 } 173 174 /** 175 * @hide 176 */ 177 @RestrictTo(RestrictTo.Scope.LIBRARY) getMaxHeight()178 public int getMaxHeight() { 179 return mMaxHeight; 180 } 181 182 /** 183 * Sets how {@link android.app.slice.SliceItem#FORMAT_ACTION} items should be handled. 184 * 185 * The default mode is {@link #MODE_THROW}. 186 * @param mode The desired mode. 187 */ setActionMode(@ormatMode int mode)188 public SerializeOptions setActionMode(@FormatMode int mode) { 189 mActionMode = mode; 190 return this; 191 } 192 193 /** 194 * Sets how {@link android.app.slice.SliceItem#FORMAT_IMAGE} items should be handled. 195 * 196 * The default mode is {@link #MODE_THROW}. 197 * @param mode The desired mode. 198 */ setImageMode(@ormatMode int mode)199 public SerializeOptions setImageMode(@FormatMode int mode) { 200 mImageMode = mode; 201 return this; 202 } 203 204 /** 205 * Set the maximum width of an image to use when serializing. 206 * <p> 207 * Will only be used if the {@link #setImageMode(int)} is set to {@link #MODE_CONVERT}. 208 * Any images larger than the maximum size will be scaled down to fit within that size. 209 * The default value is 1000. 210 */ setMaxImageWidth(int width)211 public SerializeOptions setMaxImageWidth(int width) { 212 mMaxWidth = width; 213 return this; 214 } 215 216 /** 217 * Set the maximum height of an image to use when serializing. 218 * <p> 219 * Will only be used if the {@link #setImageMode(int)} is set to {@link #MODE_CONVERT}. 220 * Any images larger than the maximum size will be scaled down to fit within that size. 221 * The default value is 1000. 222 */ setMaxImageHeight(int height)223 public SerializeOptions setMaxImageHeight(int height) { 224 mMaxHeight = height; 225 return this; 226 } 227 } 228 229 /** 230 * Indicates this slice is empty and waiting for content to be loaded. 231 * 232 * @deprecated TO BE REMOVED: use {@link SliceMetadata#LOADED_NONE} 233 */ 234 @Deprecated 235 public static final int LOADING_ALL = 0; 236 /** 237 * Indicates this slice has some content but is waiting for other content to be loaded. 238 * 239 * @deprecated TO BE REMOVED: use {@link SliceMetadata#LOADED_PARTIAL} 240 */ 241 @Deprecated 242 public static final int LOADING_PARTIAL = 1; 243 /** 244 * Indicates this slice has fully loaded and is not waiting for other content. 245 * 246 * @deprecated TO BE REMOVED: use {@link SliceMetadata#LOADED_ALL} 247 */ 248 @Deprecated 249 public static final int LOADING_COMPLETE = 2; 250 251 /** 252 * @return the current loading state of the provided {@link Slice}. 253 * 254 * @deprecated TO BE REMOVED: use {@link SliceMetadata#getLoadingState()} 255 */ 256 @Deprecated getLoadingState(@onNull Slice slice)257 public static int getLoadingState(@NonNull Slice slice) { 258 // Check loading state 259 boolean hasHintPartial = 260 SliceQuery.find(slice, null, HINT_PARTIAL, null) != null; 261 if (slice.getItems().size() == 0) { 262 // Empty slice 263 return LOADED_NONE; 264 } else if (hasHintPartial) { 265 // Slice with specific content to load 266 return LOADED_PARTIAL; 267 } else { 268 // Full slice 269 return LOADED_ALL; 270 } 271 } 272 273 /** 274 * @return the group of actions associated with the provided slice, if they exist. 275 * 276 * @deprecated TO BE REMOVED; use {@link SliceMetadata#getSliceActions()} 277 */ 278 @Deprecated 279 @Nullable getSliceActions(@onNull Slice slice)280 public static List<SliceItem> getSliceActions(@NonNull Slice slice) { 281 SliceItem actionGroup = SliceQuery.find(slice, FORMAT_SLICE, HINT_ACTIONS, null); 282 String[] hints = new String[] {HINT_ACTIONS, HINT_SHORTCUT}; 283 return (actionGroup != null) 284 ? SliceQuery.findAll(actionGroup, FORMAT_SLICE, hints, null) 285 : null; 286 } 287 288 /** 289 * @return the list of keywords associated with the provided slice, null if no keywords were 290 * specified or an empty list if the slice was specified to have no keywords. 291 * 292 * @deprecated TO BE REMOVED; use {@link SliceMetadata#getSliceKeywords()} 293 */ 294 @Deprecated 295 @Nullable getSliceKeywords(@onNull Slice slice)296 public static List<String> getSliceKeywords(@NonNull Slice slice) { 297 SliceItem keywordGroup = SliceQuery.find(slice, FORMAT_SLICE, HINT_KEYWORDS, null); 298 if (keywordGroup != null) { 299 List<SliceItem> itemList = SliceQuery.findAll(keywordGroup, FORMAT_TEXT); 300 if (itemList != null) { 301 ArrayList<String> stringList = new ArrayList<>(); 302 for (int i = 0; i < itemList.size(); i++) { 303 String keyword = (String) itemList.get(i).getText(); 304 if (!TextUtils.isEmpty(keyword)) { 305 stringList.add(keyword); 306 } 307 } 308 return stringList; 309 } 310 } 311 return null; 312 } 313 314 /** 315 * A listener used to receive events on slices parsed with 316 * {@link #parseSlice(Context, InputStream, String, SliceActionListener)}. 317 */ 318 public interface SliceActionListener { 319 /** 320 * Called when an action is triggered on a slice parsed with 321 * {@link #parseSlice(Context, InputStream, String, SliceActionListener)}. 322 * @param actionUri The uri of the action selected. 323 */ onSliceAction(Uri actionUri)324 void onSliceAction(Uri actionUri); 325 } 326 327 /** 328 * Exception thrown during 329 * {@link #parseSlice(Context, InputStream, String, SliceActionListener)}. 330 */ 331 public static class SliceParseException extends Exception { 332 /** 333 * @hide 334 */ 335 @RestrictTo(RestrictTo.Scope.LIBRARY) SliceParseException(String s, Throwable e)336 public SliceParseException(String s, Throwable e) { 337 super(s, e); 338 } 339 340 /** 341 * @hide 342 */ 343 @RestrictTo(RestrictTo.Scope.LIBRARY) SliceParseException(String s)344 public SliceParseException(String s) { 345 super(s); 346 } 347 } 348 } 349