| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.android.setupdesign.items; |
| |
| import android.content.res.Resources; |
| import android.content.res.XmlResourceParser; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.Xml; |
| import android.view.InflateException; |
| import androidx.annotation.NonNull; |
| import java.io.IOException; |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| /** |
| * A simple XML inflater, which takes care of moving the parser to the correct position. Subclasses |
| * need to implement {@link #onCreateItem(String, AttributeSet)} to create an object representation |
| * and {@link #onAddChildItem(Object, Object)} to attach a child tag to the parent tag. |
| * |
| * @param <T> The class where all instances (including child elements) belong to. If parent and |
| * child elements belong to different class hierarchies, it's OK to set this to {@link Object}. |
| */ |
| public abstract class SimpleInflater<T> { |
| |
| private static final String TAG = "SimpleInflater"; |
| private static final boolean DEBUG = false; |
| |
| protected final Resources resources; |
| |
| /** |
| * Create a new inflater instance associated with a particular Resources bundle. |
| * |
| * @param resources The Resources class used to resolve given resource IDs. |
| */ |
| protected SimpleInflater(@NonNull Resources resources) { |
| this.resources = resources; |
| } |
| |
| public Resources getResources() { |
| return resources; |
| } |
| |
| /** |
| * Inflate a new hierarchy from the specified XML resource. Throws InflaterException if there is |
| * an error. |
| * |
| * @param resId ID for an XML resource to load (e.g. <code>R.xml.my_xml</code>) |
| * @return The root of the inflated hierarchy. |
| */ |
| public T inflate(int resId) { |
| XmlResourceParser parser = getResources().getXml(resId); |
| try { |
| return inflate(parser); |
| } finally { |
| parser.close(); |
| } |
| } |
| |
| /** |
| * Inflate a new hierarchy from the specified XML node. Throws InflaterException if there is an |
| * error. |
| * |
| * <p><em><strong>Important</strong></em> For performance reasons, inflation |
| * relies heavily on pre-processing of XML files that is done at build time. Therefore, it is not |
| * currently possible to use inflater with an XmlPullParser over a plain XML file at runtime. |
| * |
| * @param parser XML dom node containing the description of the hierarchy. |
| * @return The root of the inflated hierarchy. |
| */ |
| public T inflate(XmlPullParser parser) { |
| final AttributeSet attrs = Xml.asAttributeSet(parser); |
| T createdItem; |
| |
| try { |
| // Look for the root node. |
| int type; |
| while ((type = parser.next()) != XmlPullParser.START_TAG |
| && type != XmlPullParser.END_DOCUMENT) { |
| // continue |
| } |
| |
| if (type != XmlPullParser.START_TAG) { |
| throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); |
| } |
| |
| createdItem = createItemFromTag(parser.getName(), attrs); |
| |
| rInflate(parser, createdItem, attrs); |
| } catch (XmlPullParserException e) { |
| throw new InflateException(e.getMessage(), e); |
| } catch (IOException e) { |
| throw new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); |
| } |
| |
| return createdItem; |
| } |
| |
| /** |
| * This routine is responsible for creating the correct subclass of item given the xml element |
| * name. |
| * |
| * @param tagName The XML tag name for the item to be created. |
| * @param attrs An AttributeSet of attributes to apply to the item. |
| * @return The item created. |
| */ |
| protected abstract T onCreateItem(String tagName, AttributeSet attrs); |
| |
| private T createItemFromTag(String name, AttributeSet attrs) { |
| try { |
| T item = onCreateItem(name, attrs); |
| if (DEBUG) { |
| Log.v(TAG, item + " created for <" + name + ">"); |
| } |
| return item; |
| } catch (InflateException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new InflateException( |
| attrs.getPositionDescription() + ": Error inflating class " + name, e); |
| } |
| } |
| |
| /** |
| * Recursive method used to descend down the xml hierarchy and instantiate items, instantiate |
| * their children, and then call onFinishInflate(). |
| */ |
| private void rInflate(XmlPullParser parser, T parent, final AttributeSet attrs) |
| throws XmlPullParserException, IOException { |
| final int depth = parser.getDepth(); |
| |
| int type; |
| while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) |
| && type != XmlPullParser.END_DOCUMENT) { |
| |
| if (type != XmlPullParser.START_TAG) { |
| continue; |
| } |
| |
| if (onInterceptCreateItem(parser, parent, attrs)) { |
| continue; |
| } |
| |
| String name = parser.getName(); |
| T item = createItemFromTag(name, attrs); |
| |
| onAddChildItem(parent, item); |
| |
| rInflate(parser, item, attrs); |
| } |
| } |
| |
| /** |
| * Whether item creation should be intercepted to perform custom handling on the parser rather |
| * than creating an object from it. This is used in rare cases where a tag doesn't correspond to |
| * creation of an object. |
| * |
| * <p>The parser will be pointing to the start of a tag, you must stop parsing and return when you |
| * reach the end of this element. That is, this method is responsible for parsing the element at |
| * the given position together with all of its child tags. |
| * |
| * <p>Note that parsing of the root tag cannot be intercepted. |
| * |
| * @param parser XML dom node containing the description of the hierarchy. |
| * @param parent The item that should be the parent of whatever you create. |
| * @param attrs An AttributeSet of attributes to apply to the item. |
| * @return True to continue parsing without calling {@link #onCreateItem(String, AttributeSet)}, |
| * or false if this inflater should proceed to create an item. |
| */ |
| protected boolean onInterceptCreateItem(XmlPullParser parser, T parent, AttributeSet attrs) |
| throws XmlPullParserException { |
| return false; |
| } |
| |
| protected abstract void onAddChildItem(T parent, T child); |
| } |