blob: 914bd5684b4ab8951642b6029c83b36ee4882ef1 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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
17package android.view;
18
Alan Viverettee8489cd2015-02-03 14:40:45 -080019import com.android.internal.R;
Alan Viverette0810b632014-05-01 14:42:56 -070020
Gilles Debunne30301932010-06-16 18:32:00 -070021import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
Tor Norbye7b9c9122013-05-30 16:48:33 -070024import android.annotation.LayoutRes;
Alan Viverettee8489cd2015-02-03 14:40:45 -080025import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.content.Context;
Alan Viverette0810b632014-05-01 14:42:56 -070027import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.res.TypedArray;
29import android.content.res.XmlResourceParser;
Alan Viverettee8489cd2015-02-03 14:40:45 -080030import android.graphics.Canvas;
31import android.os.Handler;
32import android.os.Message;
33import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.util.AttributeSet;
Alan Viverette0810b632014-05-01 14:42:56 -070035import android.util.Log;
Alan Viverettee8489cd2015-02-03 14:40:45 -080036import android.util.TypedValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.util.Xml;
Alan Viverettee8489cd2015-02-03 14:40:45 -080038import android.widget.FrameLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import java.io.IOException;
41import java.lang.reflect.Constructor;
42import java.util.HashMap;
43
44/**
Scott Main93dc6422012-02-24 12:04:06 -080045 * Instantiates a layout XML file into its corresponding {@link android.view.View}
46 * objects. It is never used directly. Instead, use
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 * {@link android.app.Activity#getLayoutInflater()} or
48 * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
49 * that is already hooked up to the current context and correctly configured
50 * for the device you are running on. For example:
51 *
52 * <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
Christian Mehlmauerbd6fda12011-01-08 18:22:20 +010053 * (Context.LAYOUT_INFLATER_SERVICE);</pre>
Dave Burke579e1402012-10-18 20:41:55 -070054 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055 * <p>
56 * To create a new LayoutInflater with an additional {@link Factory} for your
57 * own views, you can use {@link #cloneInContext} to clone an existing
58 * ViewFactory, and then call {@link #setFactory} on it to include your
59 * Factory.
Dave Burke579e1402012-10-18 20:41:55 -070060 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 * <p>
62 * For performance reasons, view inflation relies heavily on pre-processing of
63 * XML files that is done at build time. Therefore, it is not currently possible
64 * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
65 * it only works with an XmlPullParser returned from a compiled resource
66 * (R.<em>something</em> file.)
Dave Burke579e1402012-10-18 20:41:55 -070067 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 * @see Context#getSystemService
69 */
70public abstract class LayoutInflater {
Alan Viverette33e3cda2014-12-17 15:43:29 -080071
Alan Viverette0810b632014-05-01 14:42:56 -070072 private static final String TAG = LayoutInflater.class.getSimpleName();
73 private static final boolean DEBUG = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074
Alan Viverette8f124812015-09-25 15:17:05 -040075 /** Empty stack trace used to avoid log spam in re-throw exceptions. */
76 private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
77
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078 /**
79 * This field should be made private, so it is hidden from the SDK.
80 * {@hide}
81 */
82 protected final Context mContext;
83
84 // these are optional, set by the caller
85 private boolean mFactorySet;
86 private Factory mFactory;
Dianne Hackborn625ac272010-09-17 18:29:22 -070087 private Factory2 mFactory2;
Dianne Hackborn420829e2011-01-28 11:30:35 -080088 private Factory2 mPrivateFactory;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 private Filter mFilter;
90
Xavier Ducrohet7f9f99ea2011-08-11 10:16:17 -070091 final Object[] mConstructorArgs = new Object[2];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080092
Xavier Ducrohet7f9f99ea2011-08-11 10:16:17 -070093 static final Class<?>[] mConstructorSignature = new Class[] {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 Context.class, AttributeSet.class};
95
Gilles Debunne30301932010-06-16 18:32:00 -070096 private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
97 new HashMap<String, Constructor<? extends View>>();
Dave Burke579e1402012-10-18 20:41:55 -070098
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 private HashMap<String, Boolean> mFilterMap;
100
Alan Viverette33e3cda2014-12-17 15:43:29 -0800101 private TypedValue mTempValue;
102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 private static final String TAG_MERGE = "merge";
104 private static final String TAG_INCLUDE = "include";
Romain Guy9c1223a2011-05-17 14:25:49 -0700105 private static final String TAG_1995 = "blink";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 private static final String TAG_REQUEST_FOCUS = "requestFocus";
Alan Viverette451a3412014-02-11 18:08:46 -0800107 private static final String TAG_TAG = "tag";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108
Alan Viverette33e3cda2014-12-17 15:43:29 -0800109 private static final String ATTR_LAYOUT = "layout";
110
Alan Viveretteef259e42014-01-24 17:20:12 -0800111 private static final int[] ATTRS_THEME = new int[] {
112 com.android.internal.R.attr.theme };
Alan Viverette24927f22014-01-07 17:28:48 -0800113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 /**
115 * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
116 * to be inflated.
Dave Burke579e1402012-10-18 20:41:55 -0700117 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 */
119 public interface Filter {
120 /**
121 * Hook to allow clients of the LayoutInflater to restrict the set of Views
122 * that are allowed to be inflated.
Dave Burke579e1402012-10-18 20:41:55 -0700123 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 * @param clazz The class object for the View that is about to be inflated
Dave Burke579e1402012-10-18 20:41:55 -0700125 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 * @return True if this class is allowed to be inflated, or false otherwise
127 */
Gilles Debunnee6ac8b92010-06-17 10:55:04 -0700128 @SuppressWarnings("unchecked")
129 boolean onLoadClass(Class clazz);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 }
Dave Burke579e1402012-10-18 20:41:55 -0700131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 public interface Factory {
133 /**
134 * Hook you can supply that is called when inflating from a LayoutInflater.
135 * You can use this to customize the tag names available in your XML
136 * layout files.
Dave Burke579e1402012-10-18 20:41:55 -0700137 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 * <p>
139 * Note that it is good practice to prefix these custom names with your
140 * package (i.e., com.coolcompany.apps) to avoid conflicts with system
141 * names.
Dave Burke579e1402012-10-18 20:41:55 -0700142 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 * @param name Tag name to be inflated.
144 * @param context The context the view is being created in.
145 * @param attrs Inflation attributes as specified in XML file.
Dave Burke579e1402012-10-18 20:41:55 -0700146 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 * @return View Newly created view. Return null for the default
148 * behavior.
149 */
150 public View onCreateView(String name, Context context, AttributeSet attrs);
151 }
152
Dianne Hackborn625ac272010-09-17 18:29:22 -0700153 public interface Factory2 extends Factory {
154 /**
155 * Version of {@link #onCreateView(String, Context, AttributeSet)}
156 * that also supplies the parent that the view created view will be
157 * placed in.
158 *
159 * @param parent The parent that the created view will be placed
160 * in; <em>note that this may be null</em>.
161 * @param name Tag name to be inflated.
162 * @param context The context the view is being created in.
163 * @param attrs Inflation attributes as specified in XML file.
164 *
165 * @return View Newly created view. Return null for the default
166 * behavior.
167 */
168 public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
169 }
170
171 private static class FactoryMerger implements Factory2 {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 private final Factory mF1, mF2;
Dianne Hackborn625ac272010-09-17 18:29:22 -0700173 private final Factory2 mF12, mF22;
Dave Burke579e1402012-10-18 20:41:55 -0700174
Dianne Hackborn625ac272010-09-17 18:29:22 -0700175 FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176 mF1 = f1;
177 mF2 = f2;
Dianne Hackborn625ac272010-09-17 18:29:22 -0700178 mF12 = f12;
179 mF22 = f22;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
Dave Burke579e1402012-10-18 20:41:55 -0700181
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 public View onCreateView(String name, Context context, AttributeSet attrs) {
183 View v = mF1.onCreateView(name, context, attrs);
184 if (v != null) return v;
185 return mF2.onCreateView(name, context, attrs);
186 }
Dianne Hackborn625ac272010-09-17 18:29:22 -0700187
188 public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
189 View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
190 : mF1.onCreateView(name, context, attrs);
191 if (v != null) return v;
192 return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
193 : mF2.onCreateView(name, context, attrs);
194 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 }
Dave Burke579e1402012-10-18 20:41:55 -0700196
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 /**
198 * Create a new LayoutInflater instance associated with a particular Context.
199 * Applications will almost always want to use
200 * {@link Context#getSystemService Context.getSystemService()} to retrieve
201 * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
Dave Burke579e1402012-10-18 20:41:55 -0700202 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 * @param context The Context in which this LayoutInflater will create its
204 * Views; most importantly, this supplies the theme from which the default
205 * values for their attributes are retrieved.
206 */
207 protected LayoutInflater(Context context) {
208 mContext = context;
209 }
210
211 /**
212 * Create a new LayoutInflater instance that is a copy of an existing
213 * LayoutInflater, optionally with its Context changed. For use in
214 * implementing {@link #cloneInContext}.
Dave Burke579e1402012-10-18 20:41:55 -0700215 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 * @param original The original LayoutInflater to copy.
217 * @param newContext The new Context to use.
218 */
219 protected LayoutInflater(LayoutInflater original, Context newContext) {
220 mContext = newContext;
221 mFactory = original.mFactory;
Dianne Hackborn625ac272010-09-17 18:29:22 -0700222 mFactory2 = original.mFactory2;
Dianne Hackborn420829e2011-01-28 11:30:35 -0800223 mPrivateFactory = original.mPrivateFactory;
Dan Sandler0c7bb332014-09-18 22:11:18 -0400224 setFilter(original.mFilter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 }
Dave Burke579e1402012-10-18 20:41:55 -0700226
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 /**
228 * Obtains the LayoutInflater from the given context.
229 */
230 public static LayoutInflater from(Context context) {
231 LayoutInflater LayoutInflater =
232 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
233 if (LayoutInflater == null) {
234 throw new AssertionError("LayoutInflater not found.");
235 }
236 return LayoutInflater;
237 }
238
239 /**
240 * Create a copy of the existing LayoutInflater object, with the copy
241 * pointing to a different Context than the original. This is used by
242 * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
243 * with the new Context theme.
Dave Burke579e1402012-10-18 20:41:55 -0700244 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 * @param newContext The new Context to associate with the new LayoutInflater.
246 * May be the same as the original Context if desired.
Dave Burke579e1402012-10-18 20:41:55 -0700247 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 * @return Returns a brand spanking new LayoutInflater object associated with
249 * the given Context.
250 */
251 public abstract LayoutInflater cloneInContext(Context newContext);
Dave Burke579e1402012-10-18 20:41:55 -0700252
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 /**
254 * Return the context we are running in, for access to resources, class
255 * loader, etc.
256 */
257 public Context getContext() {
258 return mContext;
259 }
260
261 /**
Dianne Hackborn625ac272010-09-17 18:29:22 -0700262 * Return the current {@link Factory} (or null). This is called on each element
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 * name. If the factory returns a View, add that to the hierarchy. If it
264 * returns null, proceed to call onCreateView(name).
265 */
266 public final Factory getFactory() {
267 return mFactory;
268 }
269
270 /**
Dianne Hackborn625ac272010-09-17 18:29:22 -0700271 * Return the current {@link Factory2}. Returns null if no factory is set
272 * or the set factory does not implement the {@link Factory2} interface.
273 * This is called on each element
274 * name. If the factory returns a View, add that to the hierarchy. If it
275 * returns null, proceed to call onCreateView(name).
276 */
277 public final Factory2 getFactory2() {
278 return mFactory2;
279 }
280
281 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 * Attach a custom Factory interface for creating views while using
283 * this LayoutInflater. This must not be null, and can only be set once;
284 * after setting, you can not change the factory. This is
285 * called on each element name as the xml is parsed. If the factory returns
286 * a View, that is added to the hierarchy. If it returns null, the next
287 * factory default {@link #onCreateView} method is called.
Dave Burke579e1402012-10-18 20:41:55 -0700288 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 * <p>If you have an existing
290 * LayoutInflater and want to add your own factory to it, use
291 * {@link #cloneInContext} to clone the existing instance and then you
292 * can use this function (once) on the returned new instance. This will
293 * merge your own factory with whatever factory the original instance is
294 * using.
295 */
296 public void setFactory(Factory factory) {
297 if (mFactorySet) {
298 throw new IllegalStateException("A factory has already been set on this LayoutInflater");
299 }
300 if (factory == null) {
301 throw new NullPointerException("Given factory can not be null");
302 }
303 mFactorySet = true;
304 if (mFactory == null) {
305 mFactory = factory;
306 } else {
Dianne Hackborn625ac272010-09-17 18:29:22 -0700307 mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
308 }
309 }
310
311 /**
312 * Like {@link #setFactory}, but allows you to set a {@link Factory2}
313 * interface.
314 */
315 public void setFactory2(Factory2 factory) {
316 if (mFactorySet) {
317 throw new IllegalStateException("A factory has already been set on this LayoutInflater");
318 }
319 if (factory == null) {
320 throw new NullPointerException("Given factory can not be null");
321 }
322 mFactorySet = true;
323 if (mFactory == null) {
324 mFactory = mFactory2 = factory;
325 } else {
Adam Powell371a8092014-06-20 12:51:12 -0700326 mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 }
328 }
329
330 /**
Dianne Hackborn420829e2011-01-28 11:30:35 -0800331 * @hide for use by framework
332 */
333 public void setPrivateFactory(Factory2 factory) {
Adam Powell371a8092014-06-20 12:51:12 -0700334 if (mPrivateFactory == null) {
335 mPrivateFactory = factory;
336 } else {
337 mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
338 }
Dianne Hackborn420829e2011-01-28 11:30:35 -0800339 }
340
341 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
343 * that are allowed to be inflated.
344 */
345 public Filter getFilter() {
346 return mFilter;
347 }
Dave Burke579e1402012-10-18 20:41:55 -0700348
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 /**
350 * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
351 * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
352 * throw an {@link InflateException}. This filter will replace any previous filter set on this
353 * LayoutInflater.
Dave Burke579e1402012-10-18 20:41:55 -0700354 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 * @param filter The Filter which restricts the set of Views that are allowed to be inflated.
356 * This filter will replace any previous filter set on this LayoutInflater.
357 */
358 public void setFilter(Filter filter) {
359 mFilter = filter;
360 if (filter != null) {
361 mFilterMap = new HashMap<String, Boolean>();
362 }
363 }
364
365 /**
366 * Inflate a new view hierarchy from the specified xml resource. Throws
367 * {@link InflateException} if there is an error.
Dave Burke579e1402012-10-18 20:41:55 -0700368 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 * @param resource ID for an XML layout resource to load (e.g.,
370 * <code>R.layout.main_page</code>)
371 * @param root Optional view to be the parent of the generated hierarchy.
372 * @return The root View of the inflated hierarchy. If root was supplied,
373 * this is the root View; otherwise it is the root of the inflated
374 * XML file.
375 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700376 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 return inflate(resource, root, root != null);
378 }
379
380 /**
381 * Inflate a new view hierarchy from the specified xml node. Throws
382 * {@link InflateException} if there is an error. *
383 * <p>
384 * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
385 * reasons, view inflation relies heavily on pre-processing of XML files
386 * that is done at build time. Therefore, it is not currently possible to
387 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
Dave Burke579e1402012-10-18 20:41:55 -0700388 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 * @param parser XML dom node containing the description of the view
390 * hierarchy.
391 * @param root Optional view to be the parent of the generated hierarchy.
392 * @return The root View of the inflated hierarchy. If root was supplied,
393 * this is the root View; otherwise it is the root of the inflated
394 * XML file.
395 */
Scott Kennedydb5fd422015-01-15 11:56:33 -0800396 public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 return inflate(parser, root, root != null);
398 }
399
400 /**
401 * Inflate a new view hierarchy from the specified xml resource. Throws
402 * {@link InflateException} if there is an error.
Dave Burke579e1402012-10-18 20:41:55 -0700403 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 * @param resource ID for an XML layout resource to load (e.g.,
405 * <code>R.layout.main_page</code>)
406 * @param root Optional view to be the parent of the generated hierarchy (if
407 * <em>attachToRoot</em> is true), or else simply an object that
408 * provides a set of LayoutParams values for root of the returned
409 * hierarchy (if <em>attachToRoot</em> is false.)
410 * @param attachToRoot Whether the inflated hierarchy should be attached to
411 * the root parameter? If false, root is only used to create the
412 * correct subclass of LayoutParams for the root view in the XML.
413 * @return The root View of the inflated hierarchy. If root was supplied and
414 * attachToRoot is true, this is root; otherwise it is the root of
415 * the inflated XML file.
416 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700417 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
Alan Viverette0810b632014-05-01 14:42:56 -0700418 final Resources res = getContext().getResources();
419 if (DEBUG) {
420 Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
421 + Integer.toHexString(resource) + ")");
422 }
423
424 final XmlResourceParser parser = res.getLayout(resource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 try {
426 return inflate(parser, root, attachToRoot);
427 } finally {
428 parser.close();
429 }
430 }
431
432 /**
433 * Inflate a new view hierarchy from the specified XML node. Throws
434 * {@link InflateException} if there is an error.
435 * <p>
436 * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
437 * reasons, view inflation relies heavily on pre-processing of XML files
438 * that is done at build time. Therefore, it is not currently possible to
439 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
Dave Burke579e1402012-10-18 20:41:55 -0700440 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 * @param parser XML dom node containing the description of the view
442 * hierarchy.
443 * @param root Optional view to be the parent of the generated hierarchy (if
444 * <em>attachToRoot</em> is true), or else simply an object that
445 * provides a set of LayoutParams values for root of the returned
446 * hierarchy (if <em>attachToRoot</em> is false.)
447 * @param attachToRoot Whether the inflated hierarchy should be attached to
448 * the root parameter? If false, root is only used to create the
449 * correct subclass of LayoutParams for the root view in the XML.
450 * @return The root View of the inflated hierarchy. If root was supplied and
451 * attachToRoot is true, this is root; otherwise it is the root of
452 * the inflated XML file.
453 */
Scott Kennedydb5fd422015-01-15 11:56:33 -0800454 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 synchronized (mConstructorArgs) {
Romain Guy09f7b932013-04-10 11:42:44 -0700456 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
457
Alan Viverette6194d722015-03-20 15:49:06 -0700458 final Context inflaterContext = mContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 final AttributeSet attrs = Xml.asAttributeSet(parser);
Alan Viverette6194d722015-03-20 15:49:06 -0700460 Context lastContext = (Context) mConstructorArgs[0];
461 mConstructorArgs[0] = inflaterContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 View result = root;
463
464 try {
465 // Look for the root node.
466 int type;
467 while ((type = parser.next()) != XmlPullParser.START_TAG &&
468 type != XmlPullParser.END_DOCUMENT) {
469 // Empty
470 }
471
472 if (type != XmlPullParser.START_TAG) {
473 throw new InflateException(parser.getPositionDescription()
474 + ": No start tag found!");
475 }
476
477 final String name = parser.getName();
Dave Burke579e1402012-10-18 20:41:55 -0700478
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 if (DEBUG) {
480 System.out.println("**************************");
481 System.out.println("Creating root view: "
482 + name);
483 System.out.println("**************************");
484 }
485
486 if (TAG_MERGE.equals(name)) {
487 if (root == null || !attachToRoot) {
488 throw new InflateException("<merge /> can be used only with a valid "
489 + "ViewGroup root and attachToRoot=true");
490 }
491
Alan Viverette6194d722015-03-20 15:49:06 -0700492 rInflate(parser, root, inflaterContext, attrs, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 } else {
494 // Temp is the root view that was found in the xml
Alan Viverette6194d722015-03-20 15:49:06 -0700495 final View temp = createViewFromTag(root, name, inflaterContext, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496
497 ViewGroup.LayoutParams params = null;
498
499 if (root != null) {
500 if (DEBUG) {
501 System.out.println("Creating params from root: " +
502 root);
503 }
504 // Create layout params that match root, if supplied
505 params = root.generateLayoutParams(attrs);
506 if (!attachToRoot) {
507 // Set the layout params for temp if we are not
508 // attaching. (If we are, we use addView, below)
509 temp.setLayoutParams(params);
510 }
511 }
512
513 if (DEBUG) {
514 System.out.println("-----> start inflating children");
515 }
Alan Viverette6194d722015-03-20 15:49:06 -0700516
517 // Inflate all children under temp against its context.
518 rInflateChildren(parser, temp, attrs, true);
519
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 if (DEBUG) {
521 System.out.println("-----> done inflating children");
522 }
523
524 // We are supposed to attach all the views we found (int temp)
525 // to root. Do that now.
526 if (root != null && attachToRoot) {
527 root.addView(temp, params);
528 }
529
530 // Decide whether to return the root that was passed in or the
531 // top view found in xml.
532 if (root == null || !attachToRoot) {
533 result = temp;
534 }
535 }
536
537 } catch (XmlPullParserException e) {
Alan Viverette8f124812015-09-25 15:17:05 -0400538 final InflateException ie = new InflateException(e.getMessage(), e);
539 ie.setStackTrace(EMPTY_STACK_TRACE);
540 throw ie;
Alan Viverette93795052015-03-09 15:32:50 -0700541 } catch (Exception e) {
Alan Viverette8f124812015-09-25 15:17:05 -0400542 final InflateException ie = new InflateException(parser.getPositionDescription()
543 + ": " + e.getMessage(), e);
544 ie.setStackTrace(EMPTY_STACK_TRACE);
545 throw ie;
Dianne Hackborn9dae48e2010-08-26 10:20:01 -0700546 } finally {
547 // Don't retain static reference on context.
548 mConstructorArgs[0] = lastContext;
549 mConstructorArgs[1] = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550
John Reck465e1a32015-10-19 12:46:09 -0700551 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
552 }
Romain Guy09f7b932013-04-10 11:42:44 -0700553
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 return result;
555 }
556 }
557
Mathew Inwood4985c5c2015-12-31 12:28:54 +0000558 private static final ClassLoader BOOT_CLASS_LOADER = LayoutInflater.class.getClassLoader();
559
560 private final boolean verifyClassLoader(Constructor<? extends View> constructor) {
561 final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();
562 if (constructorLoader == BOOT_CLASS_LOADER) {
563 // fast path for boot class loader (most common case?) - always ok
564 return true;
565 }
566 // in all normal cases (no dynamic code loading), we will exit the following loop on the
567 // first iteration (i.e. when the declaring classloader is the contexts class loader).
568 ClassLoader cl = mContext.getClassLoader();
569 do {
570 if (constructorLoader == cl) {
571 return true;
572 }
573 cl = cl.getParent();
574 } while (cl != null);
575 return false;
576 }
577
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 /**
579 * Low-level function for instantiating a view by name. This attempts to
580 * instantiate a view class of the given <var>name</var> found in this
581 * LayoutInflater's ClassLoader.
Dave Burke579e1402012-10-18 20:41:55 -0700582 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 * <p>
584 * There are two things that can happen in an error case: either the
585 * exception describing the error will be thrown, or a null will be
586 * returned. You must deal with both possibilities -- the former will happen
587 * the first time createView() is called for a class of a particular name,
588 * the latter every time there-after for that class name.
Dave Burke579e1402012-10-18 20:41:55 -0700589 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 * @param name The full name of the class to be instantiated.
591 * @param attrs The XML attributes supplied for this instance.
Dave Burke579e1402012-10-18 20:41:55 -0700592 *
Gilles Debunne30301932010-06-16 18:32:00 -0700593 * @return View The newly instantiated view, or null.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800594 */
595 public final View createView(String name, String prefix, AttributeSet attrs)
596 throws ClassNotFoundException, InflateException {
Gilles Debunne30301932010-06-16 18:32:00 -0700597 Constructor<? extends View> constructor = sConstructorMap.get(name);
Mathew Inwood4985c5c2015-12-31 12:28:54 +0000598 if (constructor != null && !verifyClassLoader(constructor)) {
599 constructor = null;
600 sConstructorMap.remove(name);
601 }
Gilles Debunne30301932010-06-16 18:32:00 -0700602 Class<? extends View> clazz = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603
604 try {
Romain Guy09f7b932013-04-10 11:42:44 -0700605 Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
606
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800607 if (constructor == null) {
608 // Class not found in the cache, see if it's real, and try to add it
Romain Guyd03b8802009-09-16 14:36:16 -0700609 clazz = mContext.getClassLoader().loadClass(
Gilles Debunne30301932010-06-16 18:32:00 -0700610 prefix != null ? (prefix + name) : name).asSubclass(View.class);
Dave Burke579e1402012-10-18 20:41:55 -0700611
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 if (mFilter != null && clazz != null) {
613 boolean allowed = mFilter.onLoadClass(clazz);
614 if (!allowed) {
615 failNotAllowed(name, prefix, attrs);
616 }
617 }
618 constructor = clazz.getConstructor(mConstructorSignature);
Alan Viverette904de2e2015-05-04 10:32:57 -0700619 constructor.setAccessible(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620 sConstructorMap.put(name, constructor);
621 } else {
622 // If we have a filter, apply it to cached constructor
623 if (mFilter != null) {
624 // Have we seen this name before?
625 Boolean allowedState = mFilterMap.get(name);
626 if (allowedState == null) {
627 // New class -- remember whether it is allowed
Romain Guyd03b8802009-09-16 14:36:16 -0700628 clazz = mContext.getClassLoader().loadClass(
Gilles Debunne30301932010-06-16 18:32:00 -0700629 prefix != null ? (prefix + name) : name).asSubclass(View.class);
Dave Burke579e1402012-10-18 20:41:55 -0700630
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631 boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
632 mFilterMap.put(name, allowed);
633 if (!allowed) {
634 failNotAllowed(name, prefix, attrs);
635 }
636 } else if (allowedState.equals(Boolean.FALSE)) {
637 failNotAllowed(name, prefix, attrs);
638 }
639 }
640 }
641
642 Object[] args = mConstructorArgs;
643 args[1] = attrs;
Jeff Sharkeyb27b7a12012-04-02 21:07:29 -0700644
645 final View view = constructor.newInstance(args);
646 if (view instanceof ViewStub) {
Alan Viverettea9ddb8d2014-09-17 18:14:32 -0700647 // Use the same context when inflating ViewStub later.
Jeff Sharkeyb27b7a12012-04-02 21:07:29 -0700648 final ViewStub viewStub = (ViewStub) view;
Alan Viverettea9ddb8d2014-09-17 18:14:32 -0700649 viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
Jeff Sharkeyb27b7a12012-04-02 21:07:29 -0700650 }
651 return view;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652
653 } catch (NoSuchMethodException e) {
Alan Viverette8f124812015-09-25 15:17:05 -0400654 final InflateException ie = new InflateException(attrs.getPositionDescription()
655 + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
656 ie.setStackTrace(EMPTY_STACK_TRACE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800657 throw ie;
658
Gilles Debunne30301932010-06-16 18:32:00 -0700659 } catch (ClassCastException e) {
660 // If loaded class is not a View subclass
Alan Viverette8f124812015-09-25 15:17:05 -0400661 final InflateException ie = new InflateException(attrs.getPositionDescription()
662 + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
663 ie.setStackTrace(EMPTY_STACK_TRACE);
Gilles Debunne30301932010-06-16 18:32:00 -0700664 throw ie;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 } catch (ClassNotFoundException e) {
666 // If loadClass fails, we should propagate the exception.
667 throw e;
668 } catch (Exception e) {
Alan Viverette8f124812015-09-25 15:17:05 -0400669 final InflateException ie = new InflateException(
670 attrs.getPositionDescription() + ": Error inflating class "
671 + (clazz == null ? "<unknown>" : clazz.getName()), e);
672 ie.setStackTrace(EMPTY_STACK_TRACE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673 throw ie;
Romain Guy09f7b932013-04-10 11:42:44 -0700674 } finally {
675 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800676 }
677 }
678
679 /**
Gilles Debunne30301932010-06-16 18:32:00 -0700680 * Throw an exception because the specified class is not allowed to be inflated.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800681 */
682 private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
Romain Guy9c1223a2011-05-17 14:25:49 -0700683 throw new InflateException(attrs.getPositionDescription()
Alan Viverette8f124812015-09-25 15:17:05 -0400684 + ": Class not allowed to be inflated "+ (prefix != null ? (prefix + name) : name));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800685 }
686
687 /**
688 * This routine is responsible for creating the correct subclass of View
689 * given the xml element name. Override it to handle custom view objects. If
690 * you override this in your subclass be sure to call through to
691 * super.onCreateView(name) for names you do not recognize.
Dave Burke579e1402012-10-18 20:41:55 -0700692 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693 * @param name The fully qualified class name of the View to be create.
694 * @param attrs An AttributeSet of attributes to apply to the View.
Dave Burke579e1402012-10-18 20:41:55 -0700695 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 * @return View The View created.
697 */
698 protected View onCreateView(String name, AttributeSet attrs)
699 throws ClassNotFoundException {
700 return createView(name, "android.view.", attrs);
701 }
702
Dianne Hackborn625ac272010-09-17 18:29:22 -0700703 /**
704 * Version of {@link #onCreateView(String, AttributeSet)} that also
Chet Haase430742f2013-04-12 11:18:36 -0700705 * takes the future parent of the view being constructed. The default
Dianne Hackborn625ac272010-09-17 18:29:22 -0700706 * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
707 *
708 * @param parent The future parent of the returned view. <em>Note that
709 * this may be null.</em>
710 * @param name The fully qualified class name of the View to be create.
711 * @param attrs An AttributeSet of attributes to apply to the View.
712 *
713 * @return View The View created.
714 */
715 protected View onCreateView(View parent, String name, AttributeSet attrs)
716 throws ClassNotFoundException {
717 return onCreateView(name, attrs);
718 }
719
Alan Viverette24927f22014-01-07 17:28:48 -0800720 /**
Alan Viverette6194d722015-03-20 15:49:06 -0700721 * Convenience method for calling through to the five-arg createViewFromTag
722 * method. This method passes {@code false} for the {@code ignoreThemeAttr}
723 * argument and should be used for everything except {@code &gt;include>}
724 * tag parsing.
725 */
726 private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
727 return createViewFromTag(parent, name, context, attrs, false);
728 }
729
730 /**
Alan Viverette24927f22014-01-07 17:28:48 -0800731 * Creates a view from a tag name using the supplied attribute set.
732 * <p>
Alan Viverette6194d722015-03-20 15:49:06 -0700733 * <strong>Note:</strong> Default visibility so the BridgeInflater can
734 * override it.
735 *
736 * @param parent the parent view, used to inflate layout params
737 * @param name the name of the XML tag used to define the view
738 * @param context the inflation context for the view, typically the
739 * {@code parent} or base layout inflater context
740 * @param attrs the attribute set for the XML tag used to define the view
741 * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
742 * attribute (if set) for the view being inflated,
743 * {@code false} otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744 */
Alan Viverette6194d722015-03-20 15:49:06 -0700745 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
746 boolean ignoreThemeAttr) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747 if (name.equals("view")) {
748 name = attrs.getAttributeValue(null, "class");
749 }
750
Alan Viverette6194d722015-03-20 15:49:06 -0700751 // Apply a theme wrapper, if allowed and one is specified.
752 if (!ignoreThemeAttr) {
753 final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
754 final int themeResId = ta.getResourceId(0, 0);
755 if (themeResId != 0) {
756 context = new ContextThemeWrapper(context, themeResId);
757 }
758 ta.recycle();
Alan Viverette24927f22014-01-07 17:28:48 -0800759 }
760
Alan Viverette24927f22014-01-07 17:28:48 -0800761 if (name.equals(TAG_1995)) {
762 // Let's party like it's 1995!
Alan Viverette6194d722015-03-20 15:49:06 -0700763 return new BlinkLayout(context, attrs);
Alan Viverette24927f22014-01-07 17:28:48 -0800764 }
765
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800766 try {
Dianne Hackborn625ac272010-09-17 18:29:22 -0700767 View view;
Alan Viverette24927f22014-01-07 17:28:48 -0800768 if (mFactory2 != null) {
Alan Viverette6194d722015-03-20 15:49:06 -0700769 view = mFactory2.onCreateView(parent, name, context, attrs);
Alan Viverette24927f22014-01-07 17:28:48 -0800770 } else if (mFactory != null) {
Alan Viverette6194d722015-03-20 15:49:06 -0700771 view = mFactory.onCreateView(name, context, attrs);
Alan Viverette24927f22014-01-07 17:28:48 -0800772 } else {
773 view = null;
774 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800775
Dianne Hackborn420829e2011-01-28 11:30:35 -0800776 if (view == null && mPrivateFactory != null) {
Alan Viverette6194d722015-03-20 15:49:06 -0700777 view = mPrivateFactory.onCreateView(parent, name, context, attrs);
Dianne Hackborn420829e2011-01-28 11:30:35 -0800778 }
Alan Viverette24927f22014-01-07 17:28:48 -0800779
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800780 if (view == null) {
Alan Viverette24927f22014-01-07 17:28:48 -0800781 final Object lastContext = mConstructorArgs[0];
Alan Viverette6194d722015-03-20 15:49:06 -0700782 mConstructorArgs[0] = context;
Alan Viverette24927f22014-01-07 17:28:48 -0800783 try {
784 if (-1 == name.indexOf('.')) {
785 view = onCreateView(parent, name, attrs);
786 } else {
787 view = createView(name, null, attrs);
788 }
789 } finally {
790 mConstructorArgs[0] = lastContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791 }
792 }
793
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794 return view;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800795 } catch (InflateException e) {
796 throw e;
797
798 } catch (ClassNotFoundException e) {
Alan Viverette6194d722015-03-20 15:49:06 -0700799 final InflateException ie = new InflateException(attrs.getPositionDescription()
Alan Viverette8f124812015-09-25 15:17:05 -0400800 + ": Error inflating class " + name, e);
801 ie.setStackTrace(EMPTY_STACK_TRACE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 throw ie;
803
804 } catch (Exception e) {
Alan Viverette6194d722015-03-20 15:49:06 -0700805 final InflateException ie = new InflateException(attrs.getPositionDescription()
Alan Viverette8f124812015-09-25 15:17:05 -0400806 + ": Error inflating class " + name, e);
807 ie.setStackTrace(EMPTY_STACK_TRACE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808 throw ie;
809 }
810 }
811
812 /**
Alan Viverette6194d722015-03-20 15:49:06 -0700813 * Recursive method used to inflate internal (non-root) children. This
814 * method calls through to {@link #rInflate} using the parent context as
815 * the inflation context.
816 * <strong>Note:</strong> Default visibility so the BridgeInflater can
817 * call it.
818 */
819 final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
820 boolean finishInflate) throws XmlPullParserException, IOException {
821 rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
822 }
823
824 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 * Recursive method used to descend down the xml hierarchy and instantiate
826 * views, instantiate their children, and then call onFinishInflate().
Alan Viverette6194d722015-03-20 15:49:06 -0700827 * <p>
828 * <strong>Note:</strong> Default visibility so the BridgeInflater can
829 * override it.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800830 */
Alan Viverette6194d722015-03-20 15:49:06 -0700831 void rInflate(XmlPullParser parser, View parent, Context context,
832 AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800833
834 final int depth = parser.getDepth();
835 int type;
836
837 while (((type = parser.next()) != XmlPullParser.END_TAG ||
838 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
839
840 if (type != XmlPullParser.START_TAG) {
841 continue;
842 }
843
844 final String name = parser.getName();
Dave Burke579e1402012-10-18 20:41:55 -0700845
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846 if (TAG_REQUEST_FOCUS.equals(name)) {
847 parseRequestFocus(parser, parent);
Alan Viverette451a3412014-02-11 18:08:46 -0800848 } else if (TAG_TAG.equals(name)) {
849 parseViewTag(parser, parent, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 } else if (TAG_INCLUDE.equals(name)) {
851 if (parser.getDepth() == 0) {
852 throw new InflateException("<include /> cannot be the root element");
853 }
Alan Viverette6194d722015-03-20 15:49:06 -0700854 parseInclude(parser, context, parent, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800855 } else if (TAG_MERGE.equals(name)) {
856 throw new InflateException("<merge /> must be the root element");
857 } else {
Alan Viverette6194d722015-03-20 15:49:06 -0700858 final View view = createViewFromTag(parent, name, context, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 final ViewGroup viewGroup = (ViewGroup) parent;
860 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
Alan Viverette6194d722015-03-20 15:49:06 -0700861 rInflateChildren(parser, view, attrs, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 viewGroup.addView(view, params);
863 }
864 }
865
Alan Viverette6194d722015-03-20 15:49:06 -0700866 if (finishInflate) {
867 parent.onFinishInflate();
868 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 }
870
Alan Viverette451a3412014-02-11 18:08:46 -0800871 /**
872 * Parses a <code>&lt;request-focus&gt;</code> element and requests focus on
873 * the containing View.
874 */
875 private void parseRequestFocus(XmlPullParser parser, View view)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800876 throws XmlPullParserException, IOException {
Alan Viverette451a3412014-02-11 18:08:46 -0800877 view.requestFocus();
Alan Viverette6194d722015-03-20 15:49:06 -0700878
879 consumeChildElements(parser);
Alan Viverette451a3412014-02-11 18:08:46 -0800880 }
881
882 /**
883 * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
884 * containing View.
885 */
886 private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
887 throws XmlPullParserException, IOException {
Alan Viverette6194d722015-03-20 15:49:06 -0700888 final Context context = view.getContext();
889 final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
890 final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
891 final CharSequence value = ta.getText(R.styleable.ViewTag_value);
Alan Viverette451a3412014-02-11 18:08:46 -0800892 view.setTag(key, value);
893 ta.recycle();
894
Alan Viverette6194d722015-03-20 15:49:06 -0700895 consumeChildElements(parser);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800896 }
897
Alan Viverette6194d722015-03-20 15:49:06 -0700898 private void parseInclude(XmlPullParser parser, Context context, View parent,
899 AttributeSet attrs) throws XmlPullParserException, IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800900 int type;
901
902 if (parent instanceof ViewGroup) {
Alan Viverette6194d722015-03-20 15:49:06 -0700903 // Apply a theme wrapper, if requested. This is sort of a weird
904 // edge case, since developers think the <include> overwrites
905 // values in the AttributeSet of the included View. So, if the
906 // included View has a theme attribute, we'll need to ignore it.
Alan Viverette33e3cda2014-12-17 15:43:29 -0800907 final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
908 final int themeResId = ta.getResourceId(0, 0);
Alan Viverette6194d722015-03-20 15:49:06 -0700909 final boolean hasThemeOverride = themeResId != 0;
910 if (hasThemeOverride) {
Alan Viverette33e3cda2014-12-17 15:43:29 -0800911 context = new ContextThemeWrapper(context, themeResId);
912 }
913 ta.recycle();
914
915 // If the layout is pointing to a theme attribute, we have to
916 // massage the value to get a resource identifier out of it.
917 int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800918 if (layout == 0) {
Alan Viverette33e3cda2014-12-17 15:43:29 -0800919 final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
Alan Viverette6194d722015-03-20 15:49:06 -0700920 if (value == null || value.length() <= 0) {
Alan Viverette33e3cda2014-12-17 15:43:29 -0800921 throw new InflateException("You must specify a layout in the"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800922 + " include tag: <include layout=\"@layout/layoutID\" />");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 }
Alan Viverette33e3cda2014-12-17 15:43:29 -0800924
Alan Viverette6194d722015-03-20 15:49:06 -0700925 // Attempt to resolve the "?attr/name" string to an identifier.
Alan Viverette33e3cda2014-12-17 15:43:29 -0800926 layout = context.getResources().getIdentifier(value.substring(1), null, null);
927 }
928
929 // The layout might be referencing a theme attribute.
930 if (mTempValue == null) {
931 mTempValue = new TypedValue();
932 }
933 if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
934 layout = mTempValue.resourceId;
935 }
936
937 if (layout == 0) {
938 final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
939 throw new InflateException("You must specify a valid layout "
940 + "reference. The layout ID " + value + " is not valid.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 } else {
Alan Viverette6194d722015-03-20 15:49:06 -0700942 final XmlResourceParser childParser = context.getResources().getLayout(layout);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800943
944 try {
945 final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
946
947 while ((type = childParser.next()) != XmlPullParser.START_TAG &&
948 type != XmlPullParser.END_DOCUMENT) {
949 // Empty.
950 }
951
952 if (type != XmlPullParser.START_TAG) {
953 throw new InflateException(childParser.getPositionDescription() +
954 ": No start tag found!");
955 }
956
957 final String childName = childParser.getName();
958
959 if (TAG_MERGE.equals(childName)) {
Alan Viverette6194d722015-03-20 15:49:06 -0700960 // The <merge> tag doesn't support android:theme, so
961 // nothing special to do here.
962 rInflate(childParser, parent, context, childAttrs, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963 } else {
Alan Viverette6194d722015-03-20 15:49:06 -0700964 final View view = createViewFromTag(parent, childName,
965 context, childAttrs, hasThemeOverride);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800966 final ViewGroup group = (ViewGroup) parent;
967
Alan Viverettee8489cd2015-02-03 14:40:45 -0800968 final TypedArray a = context.obtainStyledAttributes(
969 attrs, R.styleable.Include);
970 final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
971 final int visibility = a.getInt(R.styleable.Include_visibility, -1);
Alan Viverettee8489cd2015-02-03 14:40:45 -0800972 a.recycle();
973
Alan Viverette84aa2fb2015-04-30 12:33:54 -0700974 // We try to load the layout params set in the <include /> tag.
975 // If the parent can't generate layout params (ex. missing width
976 // or height for the framework ViewGroups, though this is not
977 // necessarily true of all ViewGroups) then we expect it to throw
978 // a runtime exception.
979 // We catch this exception and set localParams accordingly: true
980 // means we successfully loaded layout params from the <include>
981 // tag, false means we need to rely on the included layout params.
Dave Burke579e1402012-10-18 20:41:55 -0700982 ViewGroup.LayoutParams params = null;
Alan Viverette84aa2fb2015-04-30 12:33:54 -0700983 try {
984 params = group.generateLayoutParams(attrs);
985 } catch (RuntimeException e) {
986 // Ignore, just fail over to child attrs.
Dave Burke579e1402012-10-18 20:41:55 -0700987 }
Alan Viverettee8489cd2015-02-03 14:40:45 -0800988 if (params == null) {
989 params = group.generateLayoutParams(childAttrs);
990 }
991 view.setLayoutParams(params);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800992
993 // Inflate all children.
Alan Viverette6194d722015-03-20 15:49:06 -0700994 rInflateChildren(childParser, view, childAttrs, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800995
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800996 if (id != View.NO_ID) {
997 view.setId(id);
998 }
999
1000 switch (visibility) {
1001 case 0:
1002 view.setVisibility(View.VISIBLE);
1003 break;
1004 case 1:
1005 view.setVisibility(View.INVISIBLE);
1006 break;
1007 case 2:
1008 view.setVisibility(View.GONE);
1009 break;
1010 }
1011
1012 group.addView(view);
1013 }
1014 } finally {
1015 childParser.close();
1016 }
1017 }
1018 } else {
1019 throw new InflateException("<include /> can only be used inside of a ViewGroup");
1020 }
1021
Alan Viverette6194d722015-03-20 15:49:06 -07001022 LayoutInflater.consumeChildElements(parser);
1023 }
1024
1025 /**
1026 * <strong>Note:</strong> default visibility so that
1027 * LayoutInflater_Delegate can call it.
1028 */
1029 final static void consumeChildElements(XmlPullParser parser)
1030 throws XmlPullParserException, IOException {
1031 int type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001032 final int currentDepth = parser.getDepth();
1033 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1034 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
1035 // Empty
1036 }
Romain Guy9c1223a2011-05-17 14:25:49 -07001037 }
1038
1039 private static class BlinkLayout extends FrameLayout {
1040 private static final int MESSAGE_BLINK = 0x42;
1041 private static final int BLINK_DELAY = 500;
1042
1043 private boolean mBlink;
1044 private boolean mBlinkState;
1045 private final Handler mHandler;
1046
1047 public BlinkLayout(Context context, AttributeSet attrs) {
1048 super(context, attrs);
1049 mHandler = new Handler(new Handler.Callback() {
1050 @Override
1051 public boolean handleMessage(Message msg) {
1052 if (msg.what == MESSAGE_BLINK) {
1053 if (mBlink) {
1054 mBlinkState = !mBlinkState;
1055 makeBlink();
1056 }
1057 invalidate();
1058 return true;
1059 }
1060 return false;
1061 }
1062 });
1063 }
1064
1065 private void makeBlink() {
1066 Message message = mHandler.obtainMessage(MESSAGE_BLINK);
1067 mHandler.sendMessageDelayed(message, BLINK_DELAY);
1068 }
1069
1070 @Override
1071 protected void onAttachedToWindow() {
1072 super.onAttachedToWindow();
1073
1074 mBlink = true;
1075 mBlinkState = true;
1076
1077 makeBlink();
1078 }
1079
1080 @Override
1081 protected void onDetachedFromWindow() {
1082 super.onDetachedFromWindow();
1083
1084 mBlink = false;
1085 mBlinkState = true;
1086
1087 mHandler.removeMessages(MESSAGE_BLINK);
1088 }
1089
1090 @Override
1091 protected void dispatchDraw(Canvas canvas) {
1092 if (mBlinkState) {
1093 super.dispatchDraw(canvas);
1094 }
1095 }
1096 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001097}