1c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam/*
2c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Copyright (C) 2017 The Android Open Source Project
3c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam *
4c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Licensed under the Apache License, Version 2.0 (the "License");
5c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * you may not use this file except in compliance with the License.
6c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * You may obtain a copy of the License at
7c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam *
8c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam *      http://www.apache.org/licenses/LICENSE-2.0
9c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam *
10c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Unless required by applicable law or agreed to in writing, software
11c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * distributed under the License is distributed on an "AS IS" BASIS,
12c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * See the License for the specific language governing permissions and
14c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * limitations under the License.
15c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam */
16c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
17c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lampackage com.android.setupwizardlib.items;
18c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
19c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.content.res.Resources;
20c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.content.res.XmlResourceParser;
21c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.support.annotation.NonNull;
22c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.util.AttributeSet;
23c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.util.Log;
24c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.util.Xml;
25c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.view.InflateException;
26c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
27c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport org.xmlpull.v1.XmlPullParser;
28c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport org.xmlpull.v1.XmlPullParserException;
29c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
30c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport java.io.IOException;
31c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
32c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam/**
33c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * A simple XML inflater, which takes care of moving the parser to the correct position. Subclasses
34c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * need to implement {@link #onCreateItem(String, AttributeSet)} to create an object representation
35c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * and {@link #onAddChildItem(Object, Object)} to attach a child tag to the parent tag.
36c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam *
37c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * @param <T> The class where all instances (including child elements) belong to. If parent and
38c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam *     child elements belong to different class hierarchies, it's OK to set this to {@link Object}.
39c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam */
40c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lampublic abstract class SimpleInflater<T> {
41c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
42c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    private static final String TAG = "SimpleInflater";
43d33fc41d66b7f2435bee7981613c62317d3a21fcMaurice Lam    private static final boolean DEBUG = false;
44c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
45c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    protected final Resources mResources;
46c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
47c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    /**
48c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * Create a new inflater instance associated with a particular Resources bundle.
49c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     *
50c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @param resources The Resources class used to resolve given resource IDs.
51c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     */
52c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    protected SimpleInflater(@NonNull Resources resources) {
53c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        mResources = resources;
54c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    }
55c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
56c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    public Resources getResources() {
57c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        return mResources;
58c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    }
59c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
60c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    /**
61c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * Inflate a new hierarchy from the specified XML resource. Throws InflaterException if there is
62c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * an error.
63c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     *
64c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @param resId ID for an XML resource to load (e.g. <code>R.xml.my_xml</code>)
65c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @return The root of the inflated hierarchy.
66c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     */
67c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    public T inflate(int resId) {
68c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        XmlResourceParser parser = getResources().getXml(resId);
69c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        try {
70c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            return inflate(parser);
71c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        } finally {
72c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            parser.close();
73c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        }
74c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    }
75c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
76c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    /**
77c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * Inflate a new hierarchy from the specified XML node. Throws InflaterException if there is an
78c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * error.
79c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * <p>
80c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
81c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * reasons, inflation relies heavily on pre-processing of XML files
82c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * that is done at build time. Therefore, it is not currently possible to
83c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * use inflater with an XmlPullParser over a plain XML file at runtime.
84c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     *
85c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @param parser XML dom node containing the description of the hierarchy.
86c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @return The root of the inflated hierarchy.
87c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     */
88c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    public T inflate(XmlPullParser parser) {
89c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        final AttributeSet attrs = Xml.asAttributeSet(parser);
90c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        T createdItem;
91c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
92c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        try {
93c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            // Look for the root node.
94c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            int type;
95c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            while ((type = parser.next()) != XmlPullParser.START_TAG
96c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam                    && type != XmlPullParser.END_DOCUMENT) {
97c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam                // continue
98c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            }
99c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
100c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            if (type != XmlPullParser.START_TAG) {
101c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam                throw new InflateException(parser.getPositionDescription()
102c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam                        + ": No start tag found!");
103c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            }
104c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
105c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            createdItem = createItemFromTag(parser.getName(), attrs);
106c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
107c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            rInflate(parser, createdItem, attrs);
108c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        } catch (XmlPullParserException e) {
109c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            throw new InflateException(e.getMessage(), e);
110c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        } catch (IOException e) {
111c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            throw new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e);
112c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        }
113c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
114c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        return createdItem;
115c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    }
116c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
117c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    /**
118c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * This routine is responsible for creating the correct subclass of item
119c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * given the xml element name.
120c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     *
121c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @param tagName The XML tag name for the item to be created.
122c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @param attrs An AttributeSet of attributes to apply to the item.
123c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @return The item created.
124c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     */
125c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    protected abstract T onCreateItem(String tagName, AttributeSet attrs);
126c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
127c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    private T createItemFromTag(String name, AttributeSet attrs) {
128c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        try {
129c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            T item = onCreateItem(name, attrs);
130c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            if (DEBUG) Log.v(TAG, item + " created for <" + name + ">");
131c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            return item;
132c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        } catch (InflateException e) {
133c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            throw e;
134c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        } catch (Exception e) {
135c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            throw new InflateException(attrs.getPositionDescription()
136c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam                    + ": Error inflating class " + name, e);
137c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        }
138c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    }
139c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
140c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    /**
141c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * Recursive method used to descend down the xml hierarchy and instantiate
142c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * items, instantiate their children, and then call onFinishInflate().
143c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     */
144c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    private void rInflate(XmlPullParser parser, T parent, final AttributeSet attrs)
145c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            throws XmlPullParserException, IOException {
146c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        final int depth = parser.getDepth();
147c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
148c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        int type;
149c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        while (((type = parser.next()) != XmlPullParser.END_TAG
150c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam                || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
151c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
152c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            if (type != XmlPullParser.START_TAG) {
153c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam                continue;
154c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            }
155c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
156c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            if (onInterceptCreateItem(parser, parent, attrs)) {
157c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam                continue;
158c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            }
159c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
160c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            String name = parser.getName();
161c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            T item = createItemFromTag(name, attrs);
162c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
163c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            onAddChildItem(parent, item);
164c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
165c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            rInflate(parser, item, attrs);
166c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        }
167c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    }
168c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
169c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    /**
170c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * Whether item creation should be intercepted to perform custom handling on the parser rather
171c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * than creating an object from it. This is used in rare cases where a tag doesn't correspond
172c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * to creation of an object.
173c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     *
174c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * The parser will be pointing to the start of a tag, you must stop parsing and return when you
175c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * reach the end of this element. That is, this method is responsible for parsing the element
176c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * at the given position together with all of its child tags.
177c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     *
178c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * Note that parsing of the root tag cannot be intercepted.
179c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     *
180c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @param parser XML dom node containing the description of the hierarchy.
181c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @param parent The item that should be the parent of whatever you create.
182c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @param attrs An AttributeSet of attributes to apply to the item.
183c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     * @return True to continue parsing without calling {@link #onCreateItem(String, AttributeSet)},
184c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     *     or false if this inflater should proceed to create an item.
185c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam     */
186c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    protected boolean onInterceptCreateItem(XmlPullParser parser, T parent, AttributeSet attrs)
187c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam            throws XmlPullParserException {
188c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam        return false;
189c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    }
190c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam
191c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam    protected abstract void onAddChildItem(T parent, T child);
192c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam}
193