SliceXml.java revision 07a4a56611cc044fd48b052db05aea332201216e
1901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk/* 2901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * Copyright 2017 The Android Open Source Project 3901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * 4901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * Licensed under the Apache License, Version 2.0 (the "License"); 5901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * you may not use this file except in compliance with the License. 6901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * You may obtain a copy of the License at 7901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * 8901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * http://www.apache.org/licenses/LICENSE-2.0 9901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * 10901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * Unless required by applicable law or agreed to in writing, software 11901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * distributed under the License is distributed on an "AS IS" BASIS, 12901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * See the License for the specific language governing permissions and 14901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * limitations under the License. 15901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk */ 16901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 1785ef1446b82c8783a50af92c4cb1389fe0d0e907Aurimas Liutikaspackage androidx.slice; 18901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 19901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport static org.xmlpull.v1.XmlPullParser.START_TAG; 20901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport static org.xmlpull.v1.XmlPullParser.TEXT; 21901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 22901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.annotation.SuppressLint; 2307a4a56611cc044fd48b052db05aea332201216eJason Monkimport android.content.ContentResolver; 24901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.content.Context; 2507a4a56611cc044fd48b052db05aea332201216eJason Monkimport android.content.pm.PackageManager; 2607a4a56611cc044fd48b052db05aea332201216eJason Monkimport android.content.res.Resources; 27901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.graphics.Bitmap; 2807a4a56611cc044fd48b052db05aea332201216eJason Monkimport android.graphics.BitmapFactory; 2907a4a56611cc044fd48b052db05aea332201216eJason Monkimport android.graphics.Canvas; 30901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.graphics.drawable.Drawable; 3107a4a56611cc044fd48b052db05aea332201216eJason Monkimport android.graphics.drawable.Icon; 32901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.net.Uri; 33901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.text.Html; 34901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.text.Spanned; 35901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.text.TextUtils; 3607a4a56611cc044fd48b052db05aea332201216eJason Monkimport android.util.Base64; 37901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 383ec422a2e2a46b51d4cc6926fcaa35caacbdf98dJason Monkimport androidx.annotation.RestrictTo; 393ec422a2e2a46b51d4cc6926fcaa35caacbdf98dJason Monkimport androidx.core.graphics.drawable.IconCompat; 4007a4a56611cc044fd48b052db05aea332201216eJason Monkimport androidx.core.util.Consumer; 413ec422a2e2a46b51d4cc6926fcaa35caacbdf98dJason Monk 42901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport org.xmlpull.v1.XmlPullParser; 43901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport org.xmlpull.v1.XmlPullParserException; 44901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport org.xmlpull.v1.XmlPullParserFactory; 45901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport org.xmlpull.v1.XmlSerializer; 46901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 4707a4a56611cc044fd48b052db05aea332201216eJason Monkimport java.io.ByteArrayOutputStream; 48901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport java.io.IOException; 49901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport java.io.InputStream; 50901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport java.io.OutputStream; 51901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport java.util.List; 52901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 53901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk/** 54901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * @hide 55901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk */ 56901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk@RestrictTo(RestrictTo.Scope.LIBRARY) 57901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkclass SliceXml { 58901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 59901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String NAMESPACE = null; 60901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 61901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String TAG_SLICE = "slice"; 6207a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String TAG_ACTION = "action"; 63901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String TAG_ITEM = "item"; 64901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 65901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String ATTR_URI = "uri"; 66901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String ATTR_FORMAT = "format"; 67901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String ATTR_SUBTYPE = "subtype"; 68901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String ATTR_HINTS = "hints"; 6907a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ATTR_ICON_TYPE = "iconType"; 7007a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ATTR_ICON_PACKAGE = "pkg"; 7107a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ATTR_ICON_RES_TYPE = "resType"; 72901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 7307a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ICON_TYPE_RES = "res"; 7407a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ICON_TYPE_URI = "uri"; 7507a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ICON_TYPE_DEFAULT = "def"; 7607a4a56611cc044fd48b052db05aea332201216eJason Monk 7707a4a56611cc044fd48b052db05aea332201216eJason Monk public static Slice parseSlice(Context context, InputStream input, 7807a4a56611cc044fd48b052db05aea332201216eJason Monk String encoding, SliceUtils.SliceActionListener listener) 7907a4a56611cc044fd48b052db05aea332201216eJason Monk throws IOException, SliceUtils.SliceParseException { 80901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk try { 81901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); 82901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk parser.setInput(input, encoding); 83901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 84901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int outerDepth = parser.getDepth(); 85901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int type; 86901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk Slice s = null; 87901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 88901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 89901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (type != START_TAG) { 90901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk continue; 91901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 9207a4a56611cc044fd48b052db05aea332201216eJason Monk s = parseSlice(context, parser, listener); 93901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 94901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk return s; 95901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } catch (XmlPullParserException e) { 96901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IOException("Unable to init XML Serialization", e); 97901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 98901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 99901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 100901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk @SuppressLint("WrongConstant") 10107a4a56611cc044fd48b052db05aea332201216eJason Monk private static Slice parseSlice(Context context, XmlPullParser parser, 10207a4a56611cc044fd48b052db05aea332201216eJason Monk SliceUtils.SliceActionListener listener) 10307a4a56611cc044fd48b052db05aea332201216eJason Monk throws IOException, XmlPullParserException, SliceUtils.SliceParseException { 10407a4a56611cc044fd48b052db05aea332201216eJason Monk if (!TAG_SLICE.equals(parser.getName()) && !TAG_ACTION.equals(parser.getName())) { 105901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IOException("Unexpected tag " + parser.getName()); 106901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 107901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int outerDepth = parser.getDepth(); 108901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int type; 109901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String uri = parser.getAttributeValue(NAMESPACE, ATTR_URI); 110901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk Slice.Builder b = new Slice.Builder(Uri.parse(uri)); 111901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String[] hints = hints(parser.getAttributeValue(NAMESPACE, ATTR_HINTS)); 112901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk b.addHints(hints); 113901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 114901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 115901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 116901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (type == START_TAG && TAG_ITEM.equals(parser.getName())) { 11707a4a56611cc044fd48b052db05aea332201216eJason Monk parseItem(context, b, parser, listener); 118901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 119901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 120901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk return b.build(); 121901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 122901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 123901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk @SuppressLint("WrongConstant") 12407a4a56611cc044fd48b052db05aea332201216eJason Monk private static void parseItem(Context context, Slice.Builder b, 12507a4a56611cc044fd48b052db05aea332201216eJason Monk XmlPullParser parser, final SliceUtils.SliceActionListener listener) 12607a4a56611cc044fd48b052db05aea332201216eJason Monk throws IOException, XmlPullParserException, SliceUtils.SliceParseException { 127901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int type; 128901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int outerDepth = parser.getDepth(); 129901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String format = parser.getAttributeValue(NAMESPACE, ATTR_FORMAT); 130901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String subtype = parser.getAttributeValue(NAMESPACE, ATTR_SUBTYPE); 131901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String hintStr = parser.getAttributeValue(NAMESPACE, ATTR_HINTS); 13207a4a56611cc044fd48b052db05aea332201216eJason Monk String iconType = parser.getAttributeValue(NAMESPACE, ATTR_ICON_TYPE); 13307a4a56611cc044fd48b052db05aea332201216eJason Monk String pkg = parser.getAttributeValue(NAMESPACE, ATTR_ICON_PACKAGE); 13407a4a56611cc044fd48b052db05aea332201216eJason Monk String resType = parser.getAttributeValue(NAMESPACE, ATTR_ICON_RES_TYPE); 135901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String[] hints = hints(hintStr); 136901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String v; 137901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 138901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 139901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (type == TEXT) { 140901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk switch (format) { 141901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_REMOTE_INPUT: 142901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk // Nothing for now. 143901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 144901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_IMAGE: 14507a4a56611cc044fd48b052db05aea332201216eJason Monk switch (iconType) { 14607a4a56611cc044fd48b052db05aea332201216eJason Monk case ICON_TYPE_RES: 14707a4a56611cc044fd48b052db05aea332201216eJason Monk String resName = parser.getText(); 14807a4a56611cc044fd48b052db05aea332201216eJason Monk try { 14907a4a56611cc044fd48b052db05aea332201216eJason Monk Resources r = context.getPackageManager() 15007a4a56611cc044fd48b052db05aea332201216eJason Monk .getResourcesForApplication(pkg); 15107a4a56611cc044fd48b052db05aea332201216eJason Monk int id = r.getIdentifier(resName, resType, pkg); 15207a4a56611cc044fd48b052db05aea332201216eJason Monk if (id != 0) { 15307a4a56611cc044fd48b052db05aea332201216eJason Monk b.addIcon(IconCompat.createWithResource( 15407a4a56611cc044fd48b052db05aea332201216eJason Monk context.createPackageContext(pkg, 0), id), subtype, 15507a4a56611cc044fd48b052db05aea332201216eJason Monk hints); 15607a4a56611cc044fd48b052db05aea332201216eJason Monk } else { 15707a4a56611cc044fd48b052db05aea332201216eJason Monk throw new SliceUtils.SliceParseException( 15807a4a56611cc044fd48b052db05aea332201216eJason Monk "Cannot find resource " + pkg + ":" + resType 15907a4a56611cc044fd48b052db05aea332201216eJason Monk + "/" + resName); 16007a4a56611cc044fd48b052db05aea332201216eJason Monk } 16107a4a56611cc044fd48b052db05aea332201216eJason Monk } catch (PackageManager.NameNotFoundException e) { 16207a4a56611cc044fd48b052db05aea332201216eJason Monk throw new SliceUtils.SliceParseException( 16307a4a56611cc044fd48b052db05aea332201216eJason Monk "Invalid icon package " + pkg, e); 16407a4a56611cc044fd48b052db05aea332201216eJason Monk } 16507a4a56611cc044fd48b052db05aea332201216eJason Monk break; 16607a4a56611cc044fd48b052db05aea332201216eJason Monk case ICON_TYPE_URI: 16707a4a56611cc044fd48b052db05aea332201216eJason Monk v = parser.getText(); 16807a4a56611cc044fd48b052db05aea332201216eJason Monk b.addIcon(IconCompat.createWithContentUri(v), subtype, hints); 16907a4a56611cc044fd48b052db05aea332201216eJason Monk break; 17007a4a56611cc044fd48b052db05aea332201216eJason Monk default: 17107a4a56611cc044fd48b052db05aea332201216eJason Monk v = parser.getText(); 17207a4a56611cc044fd48b052db05aea332201216eJason Monk byte[] data = Base64.decode(v, Base64.NO_WRAP); 17307a4a56611cc044fd48b052db05aea332201216eJason Monk Bitmap image = BitmapFactory.decodeByteArray(data, 0, data.length); 1743ec422a2e2a46b51d4cc6926fcaa35caacbdf98dJason Monk b.addIcon(IconCompat.createWithBitmap(image), subtype, hints); 17507a4a56611cc044fd48b052db05aea332201216eJason Monk break; 176901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 177901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 178901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_INT: 179901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk v = parser.getText(); 180901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk b.addInt(Integer.parseInt(v), subtype, hints); 181901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 182901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_TEXT: 183901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk v = parser.getText(); 184901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk b.addText(Html.fromHtml(v), subtype, hints); 185901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 18607a4a56611cc044fd48b052db05aea332201216eJason Monk case android.app.slice.SliceItem.FORMAT_LONG: 187901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk v = parser.getText(); 18807a4a56611cc044fd48b052db05aea332201216eJason Monk b.addLong(Long.parseLong(v), subtype, hints); 189901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 190901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk default: 191901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IllegalArgumentException("Unrecognized format " + format); 192901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 193901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } else if (type == START_TAG && TAG_SLICE.equals(parser.getName())) { 19407a4a56611cc044fd48b052db05aea332201216eJason Monk b.addSubSlice(parseSlice(context, parser, listener), subtype); 19507a4a56611cc044fd48b052db05aea332201216eJason Monk } else if (type == START_TAG && TAG_ACTION.equals(parser.getName())) { 19607a4a56611cc044fd48b052db05aea332201216eJason Monk b.addAction(new Consumer<Uri>() { 19707a4a56611cc044fd48b052db05aea332201216eJason Monk @Override 19807a4a56611cc044fd48b052db05aea332201216eJason Monk public void accept(Uri uri) { 19907a4a56611cc044fd48b052db05aea332201216eJason Monk listener.onSliceAction(uri); 20007a4a56611cc044fd48b052db05aea332201216eJason Monk } 20107a4a56611cc044fd48b052db05aea332201216eJason Monk }, parseSlice(context, parser, listener), subtype); 202901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 203901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 204901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 205901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 206901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static String[] hints(String hintStr) { 207901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk return TextUtils.isEmpty(hintStr) ? new String[0] : hintStr.split(","); 208901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 209901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 210901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk public static void serializeSlice(Slice s, Context context, OutputStream output, 211901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String encoding, SliceUtils.SerializeOptions options) throws IOException { 212901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk try { 213901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer(); 214901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.setOutput(output, encoding); 215901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.startDocument(encoding, null); 216901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 21707a4a56611cc044fd48b052db05aea332201216eJason Monk serialize(s, context, options, serializer, false, null); 218901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 219901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.endDocument(); 220901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.flush(); 221901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } catch (XmlPullParserException e) { 222901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IOException("Unable to init XML Serialization", e); 223901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 224901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 225901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 226901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static void serialize(Slice s, Context context, SliceUtils.SerializeOptions options, 22707a4a56611cc044fd48b052db05aea332201216eJason Monk XmlSerializer serializer, boolean isAction, String subType) throws IOException { 22807a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.startTag(NAMESPACE, isAction ? TAG_ACTION : TAG_SLICE); 229901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_URI, s.getUri().toString()); 23007a4a56611cc044fd48b052db05aea332201216eJason Monk if (subType != null) { 23107a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_SUBTYPE, subType); 23207a4a56611cc044fd48b052db05aea332201216eJason Monk } 233901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (!s.getHints().isEmpty()) { 234901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_HINTS, hintStr(s.getHints())); 235901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 236901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk for (SliceItem item : s.getItems()) { 237901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serialize(item, context, options, serializer); 238901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 239901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 24007a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.endTag(NAMESPACE, isAction ? TAG_ACTION : TAG_SLICE); 241901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 242901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 243901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static void serialize(SliceItem item, Context context, 244901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk SliceUtils.SerializeOptions options, XmlSerializer serializer) throws IOException { 245901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String format = item.getFormat(); 246901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk options.checkThrow(format); 247901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 248901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.startTag(NAMESPACE, TAG_ITEM); 249901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_FORMAT, format); 250901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (item.getSubType() != null) { 251901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_SUBTYPE, item.getSubType()); 252901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 253901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (!item.getHints().isEmpty()) { 254901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_HINTS, hintStr(item.getHints())); 255901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 256901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 257901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk switch (format) { 258901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_ACTION: 25907a4a56611cc044fd48b052db05aea332201216eJason Monk if (options.getActionMode() == SliceUtils.SerializeOptions.MODE_CONVERT) { 26007a4a56611cc044fd48b052db05aea332201216eJason Monk serialize(item.getSlice(), context, options, serializer, true, 26107a4a56611cc044fd48b052db05aea332201216eJason Monk item.getSubType()); 26207a4a56611cc044fd48b052db05aea332201216eJason Monk } else if (options.getActionMode() == SliceUtils.SerializeOptions.MODE_THROW) { 26307a4a56611cc044fd48b052db05aea332201216eJason Monk throw new IllegalArgumentException("Slice contains an action " + item); 264901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 265901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 266901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_REMOTE_INPUT: 267901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk // Nothing for now. 268901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 269901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_IMAGE: 27007a4a56611cc044fd48b052db05aea332201216eJason Monk if (options.getImageMode() == SliceUtils.SerializeOptions.MODE_CONVERT) { 27107a4a56611cc044fd48b052db05aea332201216eJason Monk IconCompat icon = item.getIcon(); 27207a4a56611cc044fd48b052db05aea332201216eJason Monk 27307a4a56611cc044fd48b052db05aea332201216eJason Monk switch (icon.getType()) { 27407a4a56611cc044fd48b052db05aea332201216eJason Monk case Icon.TYPE_RESOURCE: 27507a4a56611cc044fd48b052db05aea332201216eJason Monk serializeResIcon(serializer, icon, context); 27607a4a56611cc044fd48b052db05aea332201216eJason Monk break; 27707a4a56611cc044fd48b052db05aea332201216eJason Monk case Icon.TYPE_URI: 27807a4a56611cc044fd48b052db05aea332201216eJason Monk Uri uri = icon.getUri(); 27907a4a56611cc044fd48b052db05aea332201216eJason Monk if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { 28007a4a56611cc044fd48b052db05aea332201216eJason Monk serializeFileIcon(serializer, icon, context); 28107a4a56611cc044fd48b052db05aea332201216eJason Monk } else { 28207a4a56611cc044fd48b052db05aea332201216eJason Monk serializeIcon(serializer, icon, context, options); 28307a4a56611cc044fd48b052db05aea332201216eJason Monk } 28407a4a56611cc044fd48b052db05aea332201216eJason Monk break; 28507a4a56611cc044fd48b052db05aea332201216eJason Monk default: 28607a4a56611cc044fd48b052db05aea332201216eJason Monk serializeIcon(serializer, icon, context, options); 28707a4a56611cc044fd48b052db05aea332201216eJason Monk break; 288901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 28907a4a56611cc044fd48b052db05aea332201216eJason Monk } else if (options.getImageMode() == SliceUtils.SerializeOptions.MODE_THROW) { 29007a4a56611cc044fd48b052db05aea332201216eJason Monk throw new IllegalArgumentException("Slice contains an image " + item); 291901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 292901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 293901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_INT: 294901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.text(String.valueOf(item.getInt())); 295901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 296901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_SLICE: 29707a4a56611cc044fd48b052db05aea332201216eJason Monk serialize(item.getSlice(), context, options, serializer, false, item.getSubType()); 298901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 299901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_TEXT: 300901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (item.getText() instanceof Spanned) { 301901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.text(Html.toHtml((Spanned) item.getText())); 302901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } else { 303901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.text(String.valueOf(item.getText())); 304901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 305901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 30607a4a56611cc044fd48b052db05aea332201216eJason Monk case android.app.slice.SliceItem.FORMAT_LONG: 30707a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.text(String.valueOf(item.getLong())); 308901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 309901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk default: 310901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IllegalArgumentException("Unrecognized format " + format); 311901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 312901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.endTag(NAMESPACE, TAG_ITEM); 313901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 314901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 31507a4a56611cc044fd48b052db05aea332201216eJason Monk private static void serializeResIcon(XmlSerializer serializer, IconCompat icon, Context context) 31607a4a56611cc044fd48b052db05aea332201216eJason Monk throws IOException { 31707a4a56611cc044fd48b052db05aea332201216eJason Monk try { 31807a4a56611cc044fd48b052db05aea332201216eJason Monk Resources res = context.getPackageManager().getResourcesForApplication( 31907a4a56611cc044fd48b052db05aea332201216eJason Monk icon.getResPackage()); 32007a4a56611cc044fd48b052db05aea332201216eJason Monk int id = icon.getResId(); 32107a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_TYPE, ICON_TYPE_RES); 32207a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_PACKAGE, res.getResourcePackageName(id)); 32307a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_RES_TYPE, res.getResourceTypeName(id)); 32407a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.text(res.getResourceEntryName(id)); 32507a4a56611cc044fd48b052db05aea332201216eJason Monk } catch (PackageManager.NameNotFoundException e) { 32607a4a56611cc044fd48b052db05aea332201216eJason Monk throw new IllegalArgumentException("Slice contains invalid icon", e); 32707a4a56611cc044fd48b052db05aea332201216eJason Monk } 32807a4a56611cc044fd48b052db05aea332201216eJason Monk } 32907a4a56611cc044fd48b052db05aea332201216eJason Monk 33007a4a56611cc044fd48b052db05aea332201216eJason Monk private static void serializeFileIcon(XmlSerializer serializer, IconCompat icon, 33107a4a56611cc044fd48b052db05aea332201216eJason Monk Context context) throws IOException { 33207a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_TYPE, ICON_TYPE_URI); 33307a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.text(icon.getUri().toString()); 33407a4a56611cc044fd48b052db05aea332201216eJason Monk } 33507a4a56611cc044fd48b052db05aea332201216eJason Monk 33607a4a56611cc044fd48b052db05aea332201216eJason Monk private static void serializeIcon(XmlSerializer serializer, IconCompat icon, 33707a4a56611cc044fd48b052db05aea332201216eJason Monk Context context, SliceUtils.SerializeOptions options) throws IOException { 33807a4a56611cc044fd48b052db05aea332201216eJason Monk Drawable d = icon.loadDrawable(context); 33907a4a56611cc044fd48b052db05aea332201216eJason Monk int width = d.getIntrinsicWidth(); 34007a4a56611cc044fd48b052db05aea332201216eJason Monk int height = d.getIntrinsicHeight(); 34107a4a56611cc044fd48b052db05aea332201216eJason Monk if (width > options.getMaxWidth()) { 34207a4a56611cc044fd48b052db05aea332201216eJason Monk height = (int) (options.getMaxWidth() * height / (double) width); 34307a4a56611cc044fd48b052db05aea332201216eJason Monk width = options.getMaxWidth(); 34407a4a56611cc044fd48b052db05aea332201216eJason Monk } 34507a4a56611cc044fd48b052db05aea332201216eJason Monk if (height > options.getMaxHeight()) { 34607a4a56611cc044fd48b052db05aea332201216eJason Monk width = (int) (options.getMaxHeight() * width / (double) height); 34707a4a56611cc044fd48b052db05aea332201216eJason Monk height = options.getMaxHeight(); 34807a4a56611cc044fd48b052db05aea332201216eJason Monk } 34907a4a56611cc044fd48b052db05aea332201216eJason Monk Bitmap b = Bitmap.createBitmap(width, height, 35007a4a56611cc044fd48b052db05aea332201216eJason Monk Bitmap.Config.ARGB_8888); 35107a4a56611cc044fd48b052db05aea332201216eJason Monk Canvas c = new Canvas(b); 35207a4a56611cc044fd48b052db05aea332201216eJason Monk d.setBounds(0, 0, c.getWidth(), c.getHeight()); 35307a4a56611cc044fd48b052db05aea332201216eJason Monk d.draw(c); 35407a4a56611cc044fd48b052db05aea332201216eJason Monk ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 35507a4a56611cc044fd48b052db05aea332201216eJason Monk b.compress(Bitmap.CompressFormat.PNG, 100, outputStream); 35607a4a56611cc044fd48b052db05aea332201216eJason Monk b.recycle(); 35707a4a56611cc044fd48b052db05aea332201216eJason Monk 35807a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_TYPE, ICON_TYPE_DEFAULT); 35907a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.text(new String(Base64.encode(outputStream.toByteArray(), Base64.NO_WRAP))); 36007a4a56611cc044fd48b052db05aea332201216eJason Monk } 36107a4a56611cc044fd48b052db05aea332201216eJason Monk 362901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static String hintStr(List<String> hints) { 363901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk return TextUtils.join(",", hints); 364901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 3650f4ca634bbc43ddff900c35f7d2a43b55d8c830dJake Wharton 3660f4ca634bbc43ddff900c35f7d2a43b55d8c830dJake Wharton private SliceXml() { 3670f4ca634bbc43ddff900c35f7d2a43b55d8c830dJake Wharton } 368901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk} 369