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