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; 3338db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monkimport android.os.Build; 34901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.text.Html; 35901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.text.Spanned; 36901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport android.text.TextUtils; 3707a4a56611cc044fd48b052db05aea332201216eJason Monkimport android.util.Base64; 38901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 393ec422a2e2a46b51d4cc6926fcaa35caacbdf98dJason Monkimport androidx.annotation.RestrictTo; 403ec422a2e2a46b51d4cc6926fcaa35caacbdf98dJason Monkimport androidx.core.graphics.drawable.IconCompat; 4107a4a56611cc044fd48b052db05aea332201216eJason Monkimport androidx.core.util.Consumer; 423ec422a2e2a46b51d4cc6926fcaa35caacbdf98dJason Monk 43901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport org.xmlpull.v1.XmlPullParser; 44901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport org.xmlpull.v1.XmlPullParserException; 45901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport org.xmlpull.v1.XmlPullParserFactory; 46901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport org.xmlpull.v1.XmlSerializer; 47901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 4807a4a56611cc044fd48b052db05aea332201216eJason Monkimport java.io.ByteArrayOutputStream; 49901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport java.io.IOException; 50901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport java.io.InputStream; 51901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport java.io.OutputStream; 52901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkimport java.util.List; 53901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 54901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk/** 55901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk * @hide 56901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk */ 57901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk@RestrictTo(RestrictTo.Scope.LIBRARY) 58901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monkclass SliceXml { 59901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 60901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String NAMESPACE = null; 61901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 62901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String TAG_SLICE = "slice"; 6307a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String TAG_ACTION = "action"; 64901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String TAG_ITEM = "item"; 65901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 66901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String ATTR_URI = "uri"; 67901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String ATTR_FORMAT = "format"; 68901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String ATTR_SUBTYPE = "subtype"; 69901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static final String ATTR_HINTS = "hints"; 7007a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ATTR_ICON_TYPE = "iconType"; 7107a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ATTR_ICON_PACKAGE = "pkg"; 7207a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ATTR_ICON_RES_TYPE = "resType"; 73901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 7407a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ICON_TYPE_RES = "res"; 7507a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ICON_TYPE_URI = "uri"; 7607a4a56611cc044fd48b052db05aea332201216eJason Monk private static final String ICON_TYPE_DEFAULT = "def"; 7707a4a56611cc044fd48b052db05aea332201216eJason Monk 7807a4a56611cc044fd48b052db05aea332201216eJason Monk public static Slice parseSlice(Context context, InputStream input, 7907a4a56611cc044fd48b052db05aea332201216eJason Monk String encoding, SliceUtils.SliceActionListener listener) 8007a4a56611cc044fd48b052db05aea332201216eJason Monk throws IOException, SliceUtils.SliceParseException { 81901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk try { 82901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); 83901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk parser.setInput(input, encoding); 84901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 85901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int outerDepth = parser.getDepth(); 86901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int type; 87901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk Slice s = null; 88901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 89901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 90901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (type != START_TAG) { 91901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk continue; 92901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 9307a4a56611cc044fd48b052db05aea332201216eJason Monk s = parseSlice(context, parser, listener); 94901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 95901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk return s; 96901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } catch (XmlPullParserException e) { 97901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IOException("Unable to init XML Serialization", e); 98901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 99901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 100901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 101901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk @SuppressLint("WrongConstant") 10207a4a56611cc044fd48b052db05aea332201216eJason Monk private static Slice parseSlice(Context context, XmlPullParser parser, 10307a4a56611cc044fd48b052db05aea332201216eJason Monk SliceUtils.SliceActionListener listener) 10407a4a56611cc044fd48b052db05aea332201216eJason Monk throws IOException, XmlPullParserException, SliceUtils.SliceParseException { 10507a4a56611cc044fd48b052db05aea332201216eJason Monk if (!TAG_SLICE.equals(parser.getName()) && !TAG_ACTION.equals(parser.getName())) { 106901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IOException("Unexpected tag " + parser.getName()); 107901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 108901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int outerDepth = parser.getDepth(); 109901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int type; 110901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String uri = parser.getAttributeValue(NAMESPACE, ATTR_URI); 111901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk Slice.Builder b = new Slice.Builder(Uri.parse(uri)); 112901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String[] hints = hints(parser.getAttributeValue(NAMESPACE, ATTR_HINTS)); 113901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk b.addHints(hints); 114901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 115901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 116901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 117901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (type == START_TAG && TAG_ITEM.equals(parser.getName())) { 11807a4a56611cc044fd48b052db05aea332201216eJason Monk parseItem(context, b, parser, listener); 119901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 120901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 121901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk return b.build(); 122901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 123901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 12445e6d8a60387c26a7f7f26c209f54ef3023fc638Aurimas Liutikas @SuppressLint("DefaultCharset") 12507a4a56611cc044fd48b052db05aea332201216eJason Monk private static void parseItem(Context context, Slice.Builder b, 12607a4a56611cc044fd48b052db05aea332201216eJason Monk XmlPullParser parser, final SliceUtils.SliceActionListener listener) 12707a4a56611cc044fd48b052db05aea332201216eJason Monk throws IOException, XmlPullParserException, SliceUtils.SliceParseException { 128901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int type; 129901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk int outerDepth = parser.getDepth(); 130901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String format = parser.getAttributeValue(NAMESPACE, ATTR_FORMAT); 131901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String subtype = parser.getAttributeValue(NAMESPACE, ATTR_SUBTYPE); 132901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String hintStr = parser.getAttributeValue(NAMESPACE, ATTR_HINTS); 13307a4a56611cc044fd48b052db05aea332201216eJason Monk String iconType = parser.getAttributeValue(NAMESPACE, ATTR_ICON_TYPE); 13407a4a56611cc044fd48b052db05aea332201216eJason Monk String pkg = parser.getAttributeValue(NAMESPACE, ATTR_ICON_PACKAGE); 13507a4a56611cc044fd48b052db05aea332201216eJason Monk String resType = parser.getAttributeValue(NAMESPACE, ATTR_ICON_RES_TYPE); 13645e6d8a60387c26a7f7f26c209f54ef3023fc638Aurimas Liutikas @Slice.SliceHint String[] hints = hints(hintStr); 137901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String v; 138901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 139901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 140901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (type == TEXT) { 141901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk switch (format) { 142901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_REMOTE_INPUT: 143901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk // Nothing for now. 144901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 145901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_IMAGE: 14607a4a56611cc044fd48b052db05aea332201216eJason Monk switch (iconType) { 14707a4a56611cc044fd48b052db05aea332201216eJason Monk case ICON_TYPE_RES: 14807a4a56611cc044fd48b052db05aea332201216eJason Monk String resName = parser.getText(); 14907a4a56611cc044fd48b052db05aea332201216eJason Monk try { 15007a4a56611cc044fd48b052db05aea332201216eJason Monk Resources r = context.getPackageManager() 15107a4a56611cc044fd48b052db05aea332201216eJason Monk .getResourcesForApplication(pkg); 15207a4a56611cc044fd48b052db05aea332201216eJason Monk int id = r.getIdentifier(resName, resType, pkg); 15307a4a56611cc044fd48b052db05aea332201216eJason Monk if (id != 0) { 15407a4a56611cc044fd48b052db05aea332201216eJason Monk b.addIcon(IconCompat.createWithResource( 15507a4a56611cc044fd48b052db05aea332201216eJason Monk context.createPackageContext(pkg, 0), id), subtype, 15607a4a56611cc044fd48b052db05aea332201216eJason Monk hints); 15707a4a56611cc044fd48b052db05aea332201216eJason Monk } else { 15807a4a56611cc044fd48b052db05aea332201216eJason Monk throw new SliceUtils.SliceParseException( 15907a4a56611cc044fd48b052db05aea332201216eJason Monk "Cannot find resource " + pkg + ":" + resType 16007a4a56611cc044fd48b052db05aea332201216eJason Monk + "/" + resName); 16107a4a56611cc044fd48b052db05aea332201216eJason Monk } 16207a4a56611cc044fd48b052db05aea332201216eJason Monk } catch (PackageManager.NameNotFoundException e) { 16307a4a56611cc044fd48b052db05aea332201216eJason Monk throw new SliceUtils.SliceParseException( 16407a4a56611cc044fd48b052db05aea332201216eJason Monk "Invalid icon package " + pkg, e); 16507a4a56611cc044fd48b052db05aea332201216eJason Monk } 16607a4a56611cc044fd48b052db05aea332201216eJason Monk break; 16707a4a56611cc044fd48b052db05aea332201216eJason Monk case ICON_TYPE_URI: 16807a4a56611cc044fd48b052db05aea332201216eJason Monk v = parser.getText(); 16907a4a56611cc044fd48b052db05aea332201216eJason Monk b.addIcon(IconCompat.createWithContentUri(v), subtype, hints); 17007a4a56611cc044fd48b052db05aea332201216eJason Monk break; 17107a4a56611cc044fd48b052db05aea332201216eJason Monk default: 17207a4a56611cc044fd48b052db05aea332201216eJason Monk v = parser.getText(); 17307a4a56611cc044fd48b052db05aea332201216eJason Monk byte[] data = Base64.decode(v, Base64.NO_WRAP); 17407a4a56611cc044fd48b052db05aea332201216eJason Monk Bitmap image = BitmapFactory.decodeByteArray(data, 0, data.length); 1753ec422a2e2a46b51d4cc6926fcaa35caacbdf98dJason Monk b.addIcon(IconCompat.createWithBitmap(image), subtype, hints); 17607a4a56611cc044fd48b052db05aea332201216eJason Monk break; 177901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 178901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 179901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_INT: 180901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk v = parser.getText(); 181901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk b.addInt(Integer.parseInt(v), subtype, hints); 182901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 183901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_TEXT: 184901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk v = parser.getText(); 18538db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { 18638db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk // 19-21 don't allow special characters in XML, so we base64 encode it. 18738db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk v = new String(Base64.decode(v, Base64.NO_WRAP)); 18838db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk } 189901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk b.addText(Html.fromHtml(v), subtype, hints); 190901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 19107a4a56611cc044fd48b052db05aea332201216eJason Monk case android.app.slice.SliceItem.FORMAT_LONG: 192901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk v = parser.getText(); 19307a4a56611cc044fd48b052db05aea332201216eJason Monk b.addLong(Long.parseLong(v), subtype, hints); 194901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 195901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk default: 196901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IllegalArgumentException("Unrecognized format " + format); 197901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 198901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } else if (type == START_TAG && TAG_SLICE.equals(parser.getName())) { 19907a4a56611cc044fd48b052db05aea332201216eJason Monk b.addSubSlice(parseSlice(context, parser, listener), subtype); 20007a4a56611cc044fd48b052db05aea332201216eJason Monk } else if (type == START_TAG && TAG_ACTION.equals(parser.getName())) { 20107a4a56611cc044fd48b052db05aea332201216eJason Monk b.addAction(new Consumer<Uri>() { 20207a4a56611cc044fd48b052db05aea332201216eJason Monk @Override 20307a4a56611cc044fd48b052db05aea332201216eJason Monk public void accept(Uri uri) { 20407a4a56611cc044fd48b052db05aea332201216eJason Monk listener.onSliceAction(uri); 20507a4a56611cc044fd48b052db05aea332201216eJason Monk } 20607a4a56611cc044fd48b052db05aea332201216eJason Monk }, parseSlice(context, parser, listener), subtype); 207901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 208901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 209901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 210901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 21145e6d8a60387c26a7f7f26c209f54ef3023fc638Aurimas Liutikas @Slice.SliceHint 212901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static String[] hints(String hintStr) { 213901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk return TextUtils.isEmpty(hintStr) ? new String[0] : hintStr.split(","); 214901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 215901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 216901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk public static void serializeSlice(Slice s, Context context, OutputStream output, 217901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String encoding, SliceUtils.SerializeOptions options) throws IOException { 218901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk try { 219901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer(); 220901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.setOutput(output, encoding); 221901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.startDocument(encoding, null); 222901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 22307a4a56611cc044fd48b052db05aea332201216eJason Monk serialize(s, context, options, serializer, false, null); 224901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 225901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.endDocument(); 226901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.flush(); 227901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } catch (XmlPullParserException e) { 228901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IOException("Unable to init XML Serialization", e); 229901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 230901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 231901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 232901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static void serialize(Slice s, Context context, SliceUtils.SerializeOptions options, 23307a4a56611cc044fd48b052db05aea332201216eJason Monk XmlSerializer serializer, boolean isAction, String subType) throws IOException { 23407a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.startTag(NAMESPACE, isAction ? TAG_ACTION : TAG_SLICE); 235901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_URI, s.getUri().toString()); 23607a4a56611cc044fd48b052db05aea332201216eJason Monk if (subType != null) { 23707a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_SUBTYPE, subType); 23807a4a56611cc044fd48b052db05aea332201216eJason Monk } 239901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (!s.getHints().isEmpty()) { 240901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_HINTS, hintStr(s.getHints())); 241901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 242901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk for (SliceItem item : s.getItems()) { 243901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serialize(item, context, options, serializer); 244901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 245901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 24607a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.endTag(NAMESPACE, isAction ? TAG_ACTION : TAG_SLICE); 247901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 248901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 24945e6d8a60387c26a7f7f26c209f54ef3023fc638Aurimas Liutikas @SuppressWarnings("DefaultCharset") 250901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static void serialize(SliceItem item, Context context, 251901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk SliceUtils.SerializeOptions options, XmlSerializer serializer) throws IOException { 252901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk String format = item.getFormat(); 253901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk options.checkThrow(format); 254901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 255901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.startTag(NAMESPACE, TAG_ITEM); 256901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_FORMAT, format); 257901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (item.getSubType() != null) { 258901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_SUBTYPE, item.getSubType()); 259901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 260901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (!item.getHints().isEmpty()) { 261901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.attribute(NAMESPACE, ATTR_HINTS, hintStr(item.getHints())); 262901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 263901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 264901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk switch (format) { 265901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_ACTION: 26607a4a56611cc044fd48b052db05aea332201216eJason Monk if (options.getActionMode() == SliceUtils.SerializeOptions.MODE_CONVERT) { 26707a4a56611cc044fd48b052db05aea332201216eJason Monk serialize(item.getSlice(), context, options, serializer, true, 26807a4a56611cc044fd48b052db05aea332201216eJason Monk item.getSubType()); 26907a4a56611cc044fd48b052db05aea332201216eJason Monk } else if (options.getActionMode() == SliceUtils.SerializeOptions.MODE_THROW) { 27007a4a56611cc044fd48b052db05aea332201216eJason Monk throw new IllegalArgumentException("Slice contains an action " + item); 271901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 272901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 273901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_REMOTE_INPUT: 274901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk // Nothing for now. 275901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 276901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_IMAGE: 27707a4a56611cc044fd48b052db05aea332201216eJason Monk if (options.getImageMode() == SliceUtils.SerializeOptions.MODE_CONVERT) { 27807a4a56611cc044fd48b052db05aea332201216eJason Monk IconCompat icon = item.getIcon(); 27907a4a56611cc044fd48b052db05aea332201216eJason Monk 28007a4a56611cc044fd48b052db05aea332201216eJason Monk switch (icon.getType()) { 28107a4a56611cc044fd48b052db05aea332201216eJason Monk case Icon.TYPE_RESOURCE: 28207a4a56611cc044fd48b052db05aea332201216eJason Monk serializeResIcon(serializer, icon, context); 28307a4a56611cc044fd48b052db05aea332201216eJason Monk break; 28407a4a56611cc044fd48b052db05aea332201216eJason Monk case Icon.TYPE_URI: 28507a4a56611cc044fd48b052db05aea332201216eJason Monk Uri uri = icon.getUri(); 28607a4a56611cc044fd48b052db05aea332201216eJason Monk if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { 28707a4a56611cc044fd48b052db05aea332201216eJason Monk serializeFileIcon(serializer, icon, context); 28807a4a56611cc044fd48b052db05aea332201216eJason Monk } else { 28907a4a56611cc044fd48b052db05aea332201216eJason Monk serializeIcon(serializer, icon, context, options); 29007a4a56611cc044fd48b052db05aea332201216eJason Monk } 29107a4a56611cc044fd48b052db05aea332201216eJason Monk break; 29207a4a56611cc044fd48b052db05aea332201216eJason Monk default: 29307a4a56611cc044fd48b052db05aea332201216eJason Monk serializeIcon(serializer, icon, context, options); 29407a4a56611cc044fd48b052db05aea332201216eJason Monk break; 295901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 29607a4a56611cc044fd48b052db05aea332201216eJason Monk } else if (options.getImageMode() == SliceUtils.SerializeOptions.MODE_THROW) { 29707a4a56611cc044fd48b052db05aea332201216eJason Monk throw new IllegalArgumentException("Slice contains an image " + item); 298901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 299901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 300901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_INT: 301901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.text(String.valueOf(item.getInt())); 302901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 303901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_SLICE: 30407a4a56611cc044fd48b052db05aea332201216eJason Monk serialize(item.getSlice(), context, options, serializer, false, item.getSubType()); 305901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 306901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk case android.app.slice.SliceItem.FORMAT_TEXT: 307901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk if (item.getText() instanceof Spanned) { 30838db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk String text = Html.toHtml((Spanned) item.getText()); 30938db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { 31038db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk // 19-21 don't allow special characters in XML, so we base64 encode it. 31138db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk text = Base64.encodeToString(text.getBytes(), Base64.NO_WRAP); 31238db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk } 31338db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk serializer.text(text); 314901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } else { 31538db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk String text = String.valueOf(item.getText()); 31638db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { 31738db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk // 19-21 don't allow special characters in XML, so we base64 encode it. 31838db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk text = Base64.encodeToString(text.getBytes(), Base64.NO_WRAP); 31938db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk } 32038db00ef5d4b5a9d0d171f1bb15a5df567d448f6Jason Monk serializer.text(text); 321901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 322901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 32307a4a56611cc044fd48b052db05aea332201216eJason Monk case android.app.slice.SliceItem.FORMAT_LONG: 32407a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.text(String.valueOf(item.getLong())); 325901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk break; 326901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk default: 327901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk throw new IllegalArgumentException("Unrecognized format " + format); 328901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 329901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk serializer.endTag(NAMESPACE, TAG_ITEM); 330901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 331901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk 33207a4a56611cc044fd48b052db05aea332201216eJason Monk private static void serializeResIcon(XmlSerializer serializer, IconCompat icon, Context context) 33307a4a56611cc044fd48b052db05aea332201216eJason Monk throws IOException { 33407a4a56611cc044fd48b052db05aea332201216eJason Monk try { 33507a4a56611cc044fd48b052db05aea332201216eJason Monk Resources res = context.getPackageManager().getResourcesForApplication( 33607a4a56611cc044fd48b052db05aea332201216eJason Monk icon.getResPackage()); 33707a4a56611cc044fd48b052db05aea332201216eJason Monk int id = icon.getResId(); 33807a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_TYPE, ICON_TYPE_RES); 33907a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_PACKAGE, res.getResourcePackageName(id)); 34007a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_RES_TYPE, res.getResourceTypeName(id)); 34107a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.text(res.getResourceEntryName(id)); 34207a4a56611cc044fd48b052db05aea332201216eJason Monk } catch (PackageManager.NameNotFoundException e) { 34307a4a56611cc044fd48b052db05aea332201216eJason Monk throw new IllegalArgumentException("Slice contains invalid icon", e); 34407a4a56611cc044fd48b052db05aea332201216eJason Monk } 34507a4a56611cc044fd48b052db05aea332201216eJason Monk } 34607a4a56611cc044fd48b052db05aea332201216eJason Monk 34707a4a56611cc044fd48b052db05aea332201216eJason Monk private static void serializeFileIcon(XmlSerializer serializer, IconCompat icon, 34807a4a56611cc044fd48b052db05aea332201216eJason Monk Context context) throws IOException { 34907a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_TYPE, ICON_TYPE_URI); 35007a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.text(icon.getUri().toString()); 35107a4a56611cc044fd48b052db05aea332201216eJason Monk } 35207a4a56611cc044fd48b052db05aea332201216eJason Monk 35345e6d8a60387c26a7f7f26c209f54ef3023fc638Aurimas Liutikas @SuppressWarnings("DefaultCharset") 35407a4a56611cc044fd48b052db05aea332201216eJason Monk private static void serializeIcon(XmlSerializer serializer, IconCompat icon, 35507a4a56611cc044fd48b052db05aea332201216eJason Monk Context context, SliceUtils.SerializeOptions options) throws IOException { 35607a4a56611cc044fd48b052db05aea332201216eJason Monk Drawable d = icon.loadDrawable(context); 35707a4a56611cc044fd48b052db05aea332201216eJason Monk int width = d.getIntrinsicWidth(); 35807a4a56611cc044fd48b052db05aea332201216eJason Monk int height = d.getIntrinsicHeight(); 35907a4a56611cc044fd48b052db05aea332201216eJason Monk if (width > options.getMaxWidth()) { 36007a4a56611cc044fd48b052db05aea332201216eJason Monk height = (int) (options.getMaxWidth() * height / (double) width); 36107a4a56611cc044fd48b052db05aea332201216eJason Monk width = options.getMaxWidth(); 36207a4a56611cc044fd48b052db05aea332201216eJason Monk } 36307a4a56611cc044fd48b052db05aea332201216eJason Monk if (height > options.getMaxHeight()) { 36407a4a56611cc044fd48b052db05aea332201216eJason Monk width = (int) (options.getMaxHeight() * width / (double) height); 36507a4a56611cc044fd48b052db05aea332201216eJason Monk height = options.getMaxHeight(); 36607a4a56611cc044fd48b052db05aea332201216eJason Monk } 36707a4a56611cc044fd48b052db05aea332201216eJason Monk Bitmap b = Bitmap.createBitmap(width, height, 36807a4a56611cc044fd48b052db05aea332201216eJason Monk Bitmap.Config.ARGB_8888); 36907a4a56611cc044fd48b052db05aea332201216eJason Monk Canvas c = new Canvas(b); 37007a4a56611cc044fd48b052db05aea332201216eJason Monk d.setBounds(0, 0, c.getWidth(), c.getHeight()); 37107a4a56611cc044fd48b052db05aea332201216eJason Monk d.draw(c); 37207a4a56611cc044fd48b052db05aea332201216eJason Monk ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 37307a4a56611cc044fd48b052db05aea332201216eJason Monk b.compress(Bitmap.CompressFormat.PNG, 100, outputStream); 37407a4a56611cc044fd48b052db05aea332201216eJason Monk b.recycle(); 37507a4a56611cc044fd48b052db05aea332201216eJason Monk 37607a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.attribute(NAMESPACE, ATTR_ICON_TYPE, ICON_TYPE_DEFAULT); 37707a4a56611cc044fd48b052db05aea332201216eJason Monk serializer.text(new String(Base64.encode(outputStream.toByteArray(), Base64.NO_WRAP))); 37807a4a56611cc044fd48b052db05aea332201216eJason Monk } 37907a4a56611cc044fd48b052db05aea332201216eJason Monk 380901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk private static String hintStr(List<String> hints) { 381901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk return TextUtils.join(",", hints); 382901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk } 3830f4ca634bbc43ddff900c35f7d2a43b55d8c830dJake Wharton 3840f4ca634bbc43ddff900c35f7d2a43b55d8c830dJake Wharton private SliceXml() { 3850f4ca634bbc43ddff900c35f7d2a43b55d8c830dJake Wharton } 386901e2a634a8fce0c5e5acaa369e4e69326980b96Jason Monk} 387