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