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 com.android.tv.twopanelsettings.slices.compat; 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_REMOTE_INPUT; 22 import static java.lang.annotation.RetentionPolicy.SOURCE; 23 24 import android.app.PendingIntent; 25 import android.content.ActivityNotFoundException; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.graphics.Bitmap; 29 import android.net.Uri; 30 import android.os.Parcelable; 31 import androidx.annotation.IntDef; 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 import androidx.core.util.Preconditions; 35 import com.android.tv.twopanelsettings.slices.compat.core.SliceActionImpl; 36 import com.android.tv.twopanelsettings.slices.compat.core.SliceHints; 37 import java.io.BufferedInputStream; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.lang.annotation.Retention; 41 import java.nio.charset.Charset; 42 43 /** 44 * Utilities for dealing with slices. 45 * 46 * <p>Slice framework has been deprecated, it will not receive any updates moving forward. If you 47 * are looking for a framework that handles communication across apps, consider using {@link 48 * android.app.appsearch.AppSearchManager}. 49 */ 50 // @Deprecated // Supported for TV 51 public class SliceUtils { 52 SliceUtils()53 private SliceUtils() {} 54 55 /** 56 * Parse a slice that has been previously serialized. 57 * 58 * <p>Parses a slice that was serialized with {@link #serializeSlice}. 59 * 60 * <p>Note: Slices returned by this cannot be passed to {@link SliceConvert#unwrap(Slice)}. 61 * 62 * @param input The input stream to read from. 63 * @param encoding The encoding to read as. 64 * @param listener Listener used to handle actions when reconstructing the slice. 65 * @throws SliceParseException if the InputStream cannot be parsed. 66 */ parseSlice( @onNull final Context context, @NonNull InputStream input, @NonNull String encoding, @NonNull final SliceActionListener listener)67 public static @NonNull Slice parseSlice( 68 @NonNull final Context context, 69 @NonNull InputStream input, 70 @NonNull String encoding, 71 @NonNull final SliceActionListener listener) 72 throws IOException, SliceParseException { 73 BufferedInputStream bufferedInputStream = new BufferedInputStream(input); 74 Slice s = SliceXml.parseSlice(context, bufferedInputStream, encoding, listener); 75 s.mHints = ArrayUtils.appendElement(String.class, s.mHints, SliceHints.HINT_CACHED); 76 return s; 77 } 78 79 /** */ 80 // @RestrictTo(RestrictTo.Scope.LIBRARY) parseImageMode(@onNull SliceItem iconItem)81 public static int parseImageMode(@NonNull SliceItem iconItem) { 82 return SliceActionImpl.parseImageMode(iconItem); 83 } 84 fireAction( @ullable Context context, @NonNull Parcelable action, @Nullable Intent fillIn)85 public static void fireAction( 86 @Nullable Context context, @NonNull Parcelable action, @Nullable Intent fillIn) 87 throws PendingIntent.CanceledException { 88 if (action instanceof PendingIntent) { 89 ((PendingIntent) action).send(context, 0, fillIn, null, null); 90 return; 91 } 92 93 Preconditions.checkNotNull(context); 94 Intent filledIn = new Intent((Intent) action); 95 if (fillIn != null) { 96 filledIn.fillIn(fillIn, 0); 97 } 98 99 if (context.getPackageManager().resolveActivity(filledIn, 0) == null) { 100 context.sendBroadcast(filledIn); 101 return; 102 } 103 104 try { 105 context.startActivity(filledIn); 106 } catch (ActivityNotFoundException e) { 107 throw new PendingIntent.CanceledException(e); 108 } 109 } 110 doesStreamStartWith(String parcelName, BufferedInputStream inputStream)111 private static boolean doesStreamStartWith(String parcelName, BufferedInputStream inputStream) { 112 byte[] data = parcelName.getBytes(Charset.forName("UTF-16")); 113 byte[] buf = new byte[data.length]; 114 try { 115 // Read out the int size of the string. 116 if (inputStream.read(buf, 0, 4) < 0) { 117 return false; 118 } 119 if (inputStream.read(buf, 0, buf.length) < 0) { 120 return false; 121 } 122 return parcelName.equals(new String(buf, "UTF-16")); 123 } catch (IOException e) { 124 return false; 125 } 126 } 127 128 /** Holds options for how to handle SliceItems that cannot be serialized. */ 129 public static class SerializeOptions { 130 /** 131 * Constant indicating that the an {@link IllegalArgumentException} should be thrown when this 132 * format is encountered. 133 */ 134 public static final int MODE_THROW = 0; 135 136 /** Constant indicating that the SliceItem should be removed when this format is encountered. */ 137 public static final int MODE_REMOVE = 1; 138 139 /** 140 * Constant indicating that the SliceItem should be serialized as much as possible. 141 * 142 * <p>For images this means they will be attempted to be serialized. For actions, the action 143 * will be removed but the content of the action will be serialized. The action may be triggered 144 * later on a de-serialized slice by binding the slice again and activating a pending-intent at 145 * the same location as the serialized action. 146 */ 147 public static final int MODE_CONVERT = 2; 148 149 @IntDef({MODE_THROW, MODE_REMOVE, MODE_CONVERT}) 150 @Retention(SOURCE) 151 @interface FormatMode {} 152 153 private int mActionMode = MODE_THROW; 154 private int mImageMode = MODE_THROW; 155 private int mMaxWidth = 1000; 156 private int mMaxHeight = 1000; 157 158 private Bitmap.CompressFormat mFormat = Bitmap.CompressFormat.PNG; 159 private int mQuality = 100; 160 161 /** */ 162 // @RestrictTo(RestrictTo.Scope.LIBRARY) checkThrow(String format)163 public void checkThrow(String format) { 164 switch (format) { 165 case FORMAT_ACTION: 166 case FORMAT_REMOTE_INPUT: 167 if (mActionMode != MODE_THROW) { 168 return; 169 } 170 break; 171 case FORMAT_IMAGE: 172 if (mImageMode != MODE_THROW) { 173 return; 174 } 175 break; 176 default: 177 return; 178 } 179 throw new IllegalArgumentException(format + " cannot be serialized"); 180 } 181 182 /** */ 183 // @RestrictTo(RestrictTo.Scope.LIBRARY) getActionMode()184 public @FormatMode int getActionMode() { 185 return mActionMode; 186 } 187 188 /** */ 189 // @RestrictTo(RestrictTo.Scope.LIBRARY) getImageMode()190 public @FormatMode int getImageMode() { 191 return mImageMode; 192 } 193 194 /** */ 195 // @RestrictTo(RestrictTo.Scope.LIBRARY) getMaxWidth()196 public int getMaxWidth() { 197 return mMaxWidth; 198 } 199 200 /** */ 201 // @RestrictTo(RestrictTo.Scope.LIBRARY) getMaxHeight()202 public int getMaxHeight() { 203 return mMaxHeight; 204 } 205 206 /** */ 207 // @RestrictTo(RestrictTo.Scope.LIBRARY) getFormat()208 public Bitmap.CompressFormat getFormat() { 209 return mFormat; 210 } 211 212 /** */ 213 // @RestrictTo(RestrictTo.Scope.LIBRARY) getQuality()214 public int getQuality() { 215 return mQuality; 216 } 217 218 /** 219 * Sets how {@link android.app.slice.SliceItem#FORMAT_ACTION} items should be handled. 220 * 221 * <p>The default mode is {@link #MODE_THROW}. 222 * 223 * @param mode The desired mode. 224 */ setActionMode(@ormatMode int mode)225 public SerializeOptions setActionMode(@FormatMode int mode) { 226 mActionMode = mode; 227 return this; 228 } 229 230 /** 231 * Sets how {@link android.app.slice.SliceItem#FORMAT_IMAGE} items should be handled. 232 * 233 * <p>The default mode is {@link #MODE_THROW}. 234 * 235 * @param mode The desired mode. 236 */ setImageMode(@ormatMode int mode)237 public SerializeOptions setImageMode(@FormatMode int mode) { 238 mImageMode = mode; 239 return this; 240 } 241 242 /** 243 * Set the maximum width of an image to use when serializing. 244 * 245 * <p>Will only be used if the {@link #setImageMode(int)} is set to {@link #MODE_CONVERT}. Any 246 * images larger than the maximum size will be scaled down to fit within that size. The default 247 * value is 1000. 248 */ setMaxImageWidth(int width)249 public SerializeOptions setMaxImageWidth(int width) { 250 mMaxWidth = width; 251 return this; 252 } 253 254 /** 255 * Set the maximum height of an image to use when serializing. 256 * 257 * <p>Will only be used if the {@link #setImageMode(int)} is set to {@link #MODE_CONVERT}. Any 258 * images larger than the maximum size will be scaled down to fit within that size. The default 259 * value is 1000. 260 */ setMaxImageHeight(int height)261 public SerializeOptions setMaxImageHeight(int height) { 262 mMaxHeight = height; 263 return this; 264 } 265 266 /** 267 * Sets the options to use when converting icons to be serialized. Only used if the image mode 268 * is set to {@link #MODE_CONVERT}. 269 * 270 * @param format The format to encode images with, default is {@link 271 * android.graphics.Bitmap.CompressFormat#PNG}. 272 * @param quality The quality to use when encoding images. 273 */ setImageConversionFormat(Bitmap.CompressFormat format, int quality)274 public SerializeOptions setImageConversionFormat(Bitmap.CompressFormat format, int quality) { 275 mFormat = format; 276 mQuality = quality; 277 return this; 278 } 279 } 280 281 /** 282 * A listener used to receive events on slices parsed with {@link #parseSlice(Context, 283 * InputStream, String, SliceActionListener)}. 284 */ 285 public interface SliceActionListener { 286 /** 287 * Called when an action is triggered on a slice parsed with {@link #parseSlice(Context, 288 * InputStream, String, SliceActionListener)}. 289 * 290 * @param actionUri The uri of the action selected. 291 * @param context The context passed to {@link SliceItem#fireAction(Context, Intent)} 292 * @param intent The intent passed to {@link SliceItem#fireAction(Context, Intent)} 293 */ onSliceAction(Uri actionUri, Context context, Intent intent)294 void onSliceAction(Uri actionUri, Context context, Intent intent); 295 } 296 297 /** 298 * Exception thrown during {@link #parseSlice(Context, InputStream, String, SliceActionListener)}. 299 */ 300 public static class SliceParseException extends Exception { 301 /** */ 302 // @RestrictTo(RestrictTo.Scope.LIBRARY) SliceParseException(String s, Throwable e)303 public SliceParseException(String s, Throwable e) { 304 super(s, e); 305 } 306 307 /** */ 308 // @RestrictTo(RestrictTo.Scope.LIBRARY) SliceParseException(String s)309 public SliceParseException(String s) { 310 super(s); 311 } 312 } 313 } 314