1 /*
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 
17 package android.view;
18 
19 import com.android.internal.R;
20 
21 import org.xmlpull.v1.XmlPullParser;
22 import org.xmlpull.v1.XmlPullParserException;
23 
24 import android.annotation.LayoutRes;
25 import android.annotation.Nullable;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.content.res.TypedArray;
29 import android.content.res.XmlResourceParser;
30 import android.graphics.Canvas;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.os.Trace;
34 import android.util.AttributeSet;
35 import android.util.Log;
36 import android.util.TypedValue;
37 import android.util.Xml;
38 import android.widget.FrameLayout;
39 
40 import java.io.IOException;
41 import java.lang.reflect.Constructor;
42 import java.util.HashMap;
43 
44 /**
45  * Instantiates a layout XML file into its corresponding {@link android.view.View}
46  * objects. It is never used directly. Instead, use
47  * {@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
53  *      (Context.LAYOUT_INFLATER_SERVICE);</pre>
54  *
55  * <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.
60  *
61  * <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.)
67  *
68  * @see Context#getSystemService
69  */
70 public abstract class LayoutInflater {
71 
72     private static final String TAG = LayoutInflater.class.getSimpleName();
73     private static final boolean DEBUG = false;
74 
75     /** Empty stack trace used to avoid log spam in re-throw exceptions. */
76     private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
77 
78     /**
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;
87     private Factory2 mFactory2;
88     private Factory2 mPrivateFactory;
89     private Filter mFilter;
90 
91     final Object[] mConstructorArgs = new Object[2];
92 
93     static final Class<?>[] mConstructorSignature = new Class[] {
94             Context.class, AttributeSet.class};
95 
96     private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
97             new HashMap<String, Constructor<? extends View>>();
98 
99     private HashMap<String, Boolean> mFilterMap;
100 
101     private TypedValue mTempValue;
102 
103     private static final String TAG_MERGE = "merge";
104     private static final String TAG_INCLUDE = "include";
105     private static final String TAG_1995 = "blink";
106     private static final String TAG_REQUEST_FOCUS = "requestFocus";
107     private static final String TAG_TAG = "tag";
108 
109     private static final String ATTR_LAYOUT = "layout";
110 
111     private static final int[] ATTRS_THEME = new int[] {
112             com.android.internal.R.attr.theme };
113 
114     /**
115      * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
116      * to be inflated.
117      *
118      */
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.
123          *
124          * @param clazz The class object for the View that is about to be inflated
125          *
126          * @return True if this class is allowed to be inflated, or false otherwise
127          */
128         @SuppressWarnings("unchecked")
onLoadClass(Class clazz)129         boolean onLoadClass(Class clazz);
130     }
131 
132     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.
137          *
138          * <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.
142          *
143          * @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.
146          *
147          * @return View Newly created view. Return null for the default
148          *         behavior.
149          */
onCreateView(String name, Context context, AttributeSet attrs)150         public View onCreateView(String name, Context context, AttributeSet attrs);
151     }
152 
153     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          */
onCreateView(View parent, String name, Context context, AttributeSet attrs)168         public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
169     }
170 
171     private static class FactoryMerger implements Factory2 {
172         private final Factory mF1, mF2;
173         private final Factory2 mF12, mF22;
174 
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22)175         FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
176             mF1 = f1;
177             mF2 = f2;
178             mF12 = f12;
179             mF22 = f22;
180         }
181 
onCreateView(String name, Context context, AttributeSet attrs)182         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         }
187 
onCreateView(View parent, String name, Context context, AttributeSet attrs)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         }
195     }
196 
197     /**
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}.
202      *
203      * @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      */
LayoutInflater(Context context)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}.
215      *
216      * @param original The original LayoutInflater to copy.
217      * @param newContext The new Context to use.
218      */
LayoutInflater(LayoutInflater original, Context newContext)219     protected LayoutInflater(LayoutInflater original, Context newContext) {
220         mContext = newContext;
221         mFactory = original.mFactory;
222         mFactory2 = original.mFactory2;
223         mPrivateFactory = original.mPrivateFactory;
224         setFilter(original.mFilter);
225     }
226 
227     /**
228      * Obtains the LayoutInflater from the given context.
229      */
from(Context context)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.
244      *
245      * @param newContext The new Context to associate with the new LayoutInflater.
246      * May be the same as the original Context if desired.
247      *
248      * @return Returns a brand spanking new LayoutInflater object associated with
249      * the given Context.
250      */
cloneInContext(Context newContext)251     public abstract LayoutInflater cloneInContext(Context newContext);
252 
253     /**
254      * Return the context we are running in, for access to resources, class
255      * loader, etc.
256      */
getContext()257     public Context getContext() {
258         return mContext;
259     }
260 
261     /**
262      * Return the current {@link Factory} (or null). This is called on each element
263      * name. If the factory returns a View, add that to the hierarchy. If it
264      * returns null, proceed to call onCreateView(name).
265      */
getFactory()266     public final Factory getFactory() {
267         return mFactory;
268     }
269 
270     /**
271      * 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      */
getFactory2()277     public final Factory2 getFactory2() {
278         return mFactory2;
279     }
280 
281     /**
282      * 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.
288      *
289      * <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      */
setFactory(Factory factory)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 {
307             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      */
setFactory2(Factory2 factory)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 {
326             mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
327         }
328     }
329 
330     /**
331      * @hide for use by framework
332      */
setPrivateFactory(Factory2 factory)333     public void setPrivateFactory(Factory2 factory) {
334         if (mPrivateFactory == null) {
335             mPrivateFactory = factory;
336         } else {
337             mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
338         }
339     }
340 
341     /**
342      * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
343      * that are allowed to be inflated.
344      */
getFilter()345     public Filter getFilter() {
346         return mFilter;
347     }
348 
349     /**
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.
354      *
355      * @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      */
setFilter(Filter filter)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.
368      *
369      * @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      */
inflate(@ayoutRes int resource, @Nullable ViewGroup root)376     public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
377         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.
388      *
389      * @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      */
inflate(XmlPullParser parser, @Nullable ViewGroup root)396     public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
397         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.
403      *
404      * @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      */
inflate(@ayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)417     public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
418         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);
425         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.
440      *
441      * @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      */
inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)454     public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
455         synchronized (mConstructorArgs) {
456             Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
457 
458             final Context inflaterContext = mContext;
459             final AttributeSet attrs = Xml.asAttributeSet(parser);
460             Context lastContext = (Context) mConstructorArgs[0];
461             mConstructorArgs[0] = inflaterContext;
462             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();
478 
479                 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 
492                     rInflate(parser, root, inflaterContext, attrs, false);
493                 } else {
494                     // Temp is the root view that was found in the xml
495                     final View temp = createViewFromTag(root, name, inflaterContext, attrs);
496 
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                     }
516 
517                     // Inflate all children under temp against its context.
518                     rInflateChildren(parser, temp, attrs, true);
519 
520                     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) {
538                 final InflateException ie = new InflateException(e.getMessage(), e);
539                 ie.setStackTrace(EMPTY_STACK_TRACE);
540                 throw ie;
541             } catch (Exception e) {
542                 final InflateException ie = new InflateException(parser.getPositionDescription()
543                         + ": " + e.getMessage(), e);
544                 ie.setStackTrace(EMPTY_STACK_TRACE);
545                 throw ie;
546             } finally {
547                 // Don't retain static reference on context.
548                 mConstructorArgs[0] = lastContext;
549                 mConstructorArgs[1] = null;
550 
551                 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
552             }
553 
554             return result;
555         }
556     }
557 
558     private static final ClassLoader BOOT_CLASS_LOADER = LayoutInflater.class.getClassLoader();
559 
verifyClassLoader(Constructor<? extends View> constructor)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 
578     /**
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.
582      *
583      * <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.
589      *
590      * @param name The full name of the class to be instantiated.
591      * @param attrs The XML attributes supplied for this instance.
592      *
593      * @return View The newly instantiated view, or null.
594      */
createView(String name, String prefix, AttributeSet attrs)595     public final View createView(String name, String prefix, AttributeSet attrs)
596             throws ClassNotFoundException, InflateException {
597         Constructor<? extends View> constructor = sConstructorMap.get(name);
598         if (constructor != null && !verifyClassLoader(constructor)) {
599             constructor = null;
600             sConstructorMap.remove(name);
601         }
602         Class<? extends View> clazz = null;
603 
604         try {
605             Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
606 
607             if (constructor == null) {
608                 // Class not found in the cache, see if it's real, and try to add it
609                 clazz = mContext.getClassLoader().loadClass(
610                         prefix != null ? (prefix + name) : name).asSubclass(View.class);
611 
612                 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);
619                 constructor.setAccessible(true);
620                 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
628                         clazz = mContext.getClassLoader().loadClass(
629                                 prefix != null ? (prefix + name) : name).asSubclass(View.class);
630 
631                         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;
644 
645             final View view = constructor.newInstance(args);
646             if (view instanceof ViewStub) {
647                 // Use the same context when inflating ViewStub later.
648                 final ViewStub viewStub = (ViewStub) view;
649                 viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
650             }
651             return view;
652 
653         } catch (NoSuchMethodException e) {
654             final InflateException ie = new InflateException(attrs.getPositionDescription()
655                     + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
656             ie.setStackTrace(EMPTY_STACK_TRACE);
657             throw ie;
658 
659         } catch (ClassCastException e) {
660             // If loaded class is not a View subclass
661             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);
664             throw ie;
665         } catch (ClassNotFoundException e) {
666             // If loadClass fails, we should propagate the exception.
667             throw e;
668         } catch (Exception e) {
669             final InflateException ie = new InflateException(
670                     attrs.getPositionDescription() + ": Error inflating class "
671                             + (clazz == null ? "<unknown>" : clazz.getName()), e);
672             ie.setStackTrace(EMPTY_STACK_TRACE);
673             throw ie;
674         } finally {
675             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
676         }
677     }
678 
679     /**
680      * Throw an exception because the specified class is not allowed to be inflated.
681      */
failNotAllowed(String name, String prefix, AttributeSet attrs)682     private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
683         throw new InflateException(attrs.getPositionDescription()
684                 + ": Class not allowed to be inflated "+ (prefix != null ? (prefix + name) : name));
685     }
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.
692      *
693      * @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.
695      *
696      * @return View The View created.
697      */
onCreateView(String name, AttributeSet attrs)698     protected View onCreateView(String name, AttributeSet attrs)
699             throws ClassNotFoundException {
700         return createView(name, "android.view.", attrs);
701     }
702 
703     /**
704      * Version of {@link #onCreateView(String, AttributeSet)} that also
705      * takes the future parent of the view being constructed.  The default
706      * 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      */
onCreateView(View parent, String name, AttributeSet attrs)715     protected View onCreateView(View parent, String name, AttributeSet attrs)
716             throws ClassNotFoundException {
717         return onCreateView(name, attrs);
718     }
719 
720     /**
721      * 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      */
createViewFromTag(View parent, String name, Context context, AttributeSet attrs)726     private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
727         return createViewFromTag(parent, name, context, attrs, false);
728     }
729 
730     /**
731      * Creates a view from a tag name using the supplied attribute set.
732      * <p>
733      * <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
744      */
createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr)745     View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
746             boolean ignoreThemeAttr) {
747         if (name.equals("view")) {
748             name = attrs.getAttributeValue(null, "class");
749         }
750 
751         // 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();
759         }
760 
761         if (name.equals(TAG_1995)) {
762             // Let's party like it's 1995!
763             return new BlinkLayout(context, attrs);
764         }
765 
766         try {
767             View view;
768             if (mFactory2 != null) {
769                 view = mFactory2.onCreateView(parent, name, context, attrs);
770             } else if (mFactory != null) {
771                 view = mFactory.onCreateView(name, context, attrs);
772             } else {
773                 view = null;
774             }
775 
776             if (view == null && mPrivateFactory != null) {
777                 view = mPrivateFactory.onCreateView(parent, name, context, attrs);
778             }
779 
780             if (view == null) {
781                 final Object lastContext = mConstructorArgs[0];
782                 mConstructorArgs[0] = context;
783                 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;
791                 }
792             }
793 
794             return view;
795         } catch (InflateException e) {
796             throw e;
797 
798         } catch (ClassNotFoundException e) {
799             final InflateException ie = new InflateException(attrs.getPositionDescription()
800                     + ": Error inflating class " + name, e);
801             ie.setStackTrace(EMPTY_STACK_TRACE);
802             throw ie;
803 
804         } catch (Exception e) {
805             final InflateException ie = new InflateException(attrs.getPositionDescription()
806                     + ": Error inflating class " + name, e);
807             ie.setStackTrace(EMPTY_STACK_TRACE);
808             throw ie;
809         }
810     }
811 
812     /**
813      * 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      */
rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate)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     /**
825      * Recursive method used to descend down the xml hierarchy and instantiate
826      * views, instantiate their children, and then call onFinishInflate().
827      * <p>
828      * <strong>Note:</strong> Default visibility so the BridgeInflater can
829      * override it.
830      */
rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate)831     void rInflate(XmlPullParser parser, View parent, Context context,
832             AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
833 
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();
845 
846             if (TAG_REQUEST_FOCUS.equals(name)) {
847                 parseRequestFocus(parser, parent);
848             } else if (TAG_TAG.equals(name)) {
849                 parseViewTag(parser, parent, attrs);
850             } else if (TAG_INCLUDE.equals(name)) {
851                 if (parser.getDepth() == 0) {
852                     throw new InflateException("<include /> cannot be the root element");
853                 }
854                 parseInclude(parser, context, parent, attrs);
855             } else if (TAG_MERGE.equals(name)) {
856                 throw new InflateException("<merge /> must be the root element");
857             } else {
858                 final View view = createViewFromTag(parent, name, context, attrs);
859                 final ViewGroup viewGroup = (ViewGroup) parent;
860                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
861                 rInflateChildren(parser, view, attrs, true);
862                 viewGroup.addView(view, params);
863             }
864         }
865 
866         if (finishInflate) {
867             parent.onFinishInflate();
868         }
869     }
870 
871     /**
872      * Parses a <code>&lt;request-focus&gt;</code> element and requests focus on
873      * the containing View.
874      */
parseRequestFocus(XmlPullParser parser, View view)875     private void parseRequestFocus(XmlPullParser parser, View view)
876             throws XmlPullParserException, IOException {
877         view.requestFocus();
878 
879         consumeChildElements(parser);
880     }
881 
882     /**
883      * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
884      * containing View.
885      */
parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)886     private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
887             throws XmlPullParserException, IOException {
888         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);
892         view.setTag(key, value);
893         ta.recycle();
894 
895         consumeChildElements(parser);
896     }
897 
parseInclude(XmlPullParser parser, Context context, View parent, AttributeSet attrs)898     private void parseInclude(XmlPullParser parser, Context context, View parent,
899             AttributeSet attrs) throws XmlPullParserException, IOException {
900         int type;
901 
902         if (parent instanceof ViewGroup) {
903             // 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.
907             final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
908             final int themeResId = ta.getResourceId(0, 0);
909             final boolean hasThemeOverride = themeResId != 0;
910             if (hasThemeOverride) {
911                 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);
918             if (layout == 0) {
919                 final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
920                 if (value == null || value.length() <= 0) {
921                     throw new InflateException("You must specify a layout in the"
922                             + " include tag: <include layout=\"@layout/layoutID\" />");
923                 }
924 
925                 // Attempt to resolve the "?attr/name" string to an identifier.
926                 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.");
941             } else {
942                 final XmlResourceParser childParser = context.getResources().getLayout(layout);
943 
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)) {
960                         // The <merge> tag doesn't support android:theme, so
961                         // nothing special to do here.
962                         rInflate(childParser, parent, context, childAttrs, false);
963                     } else {
964                         final View view = createViewFromTag(parent, childName,
965                                 context, childAttrs, hasThemeOverride);
966                         final ViewGroup group = (ViewGroup) parent;
967 
968                         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);
972                         a.recycle();
973 
974                         // 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.
982                         ViewGroup.LayoutParams params = null;
983                         try {
984                             params = group.generateLayoutParams(attrs);
985                         } catch (RuntimeException e) {
986                             // Ignore, just fail over to child attrs.
987                         }
988                         if (params == null) {
989                             params = group.generateLayoutParams(childAttrs);
990                         }
991                         view.setLayoutParams(params);
992 
993                         // Inflate all children.
994                         rInflateChildren(childParser, view, childAttrs, true);
995 
996                         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 
1022         LayoutInflater.consumeChildElements(parser);
1023     }
1024 
1025     /**
1026      * <strong>Note:</strong> default visibility so that
1027      * LayoutInflater_Delegate can call it.
1028      */
consumeChildElements(XmlPullParser parser)1029     final static void consumeChildElements(XmlPullParser parser)
1030             throws XmlPullParserException, IOException {
1031         int type;
1032         final int currentDepth = parser.getDepth();
1033         while (((type = parser.next()) != XmlPullParser.END_TAG ||
1034                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
1035             // Empty
1036         }
1037     }
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 
BlinkLayout(Context context, AttributeSet attrs)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 
makeBlink()1065         private void makeBlink() {
1066             Message message = mHandler.obtainMessage(MESSAGE_BLINK);
1067             mHandler.sendMessageDelayed(message, BLINK_DELAY);
1068         }
1069 
1070         @Override
onAttachedToWindow()1071         protected void onAttachedToWindow() {
1072             super.onAttachedToWindow();
1073 
1074             mBlink = true;
1075             mBlinkState = true;
1076 
1077             makeBlink();
1078         }
1079 
1080         @Override
onDetachedFromWindow()1081         protected void onDetachedFromWindow() {
1082             super.onDetachedFromWindow();
1083 
1084             mBlink = false;
1085             mBlinkState = true;
1086 
1087             mHandler.removeMessages(MESSAGE_BLINK);
1088         }
1089 
1090         @Override
dispatchDraw(Canvas canvas)1091         protected void dispatchDraw(Canvas canvas) {
1092             if (mBlinkState) {
1093                 super.dispatchDraw(canvas);
1094             }
1095         }
1096     }
1097 }
1098