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> 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