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 android.graphics.Canvas;
20 import android.os.Handler;
21 import android.os.Message;
22 import android.os.Trace;
23 import android.widget.FrameLayout;
24 
25 import org.xmlpull.v1.XmlPullParser;
26 import org.xmlpull.v1.XmlPullParserException;
27 
28 import android.content.Context;
29 import android.content.res.Resources;
30 import android.content.res.TypedArray;
31 import android.content.res.XmlResourceParser;
32 import android.util.AttributeSet;
33 import android.util.Log;
34 import android.util.Xml;
35 
36 import java.io.IOException;
37 import java.lang.reflect.Constructor;
38 import java.util.HashMap;
39 
40 /**
41  * Instantiates a layout XML file into its corresponding {@link android.view.View}
42  * objects. It is never used directly. Instead, use
43  * {@link android.app.Activity#getLayoutInflater()} or
44  * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
45  * that is already hooked up to the current context and correctly configured
46  * for the device you are running on.  For example:
47  *
48  * <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
49  *      (Context.LAYOUT_INFLATER_SERVICE);</pre>
50  *
51  * <p>
52  * To create a new LayoutInflater with an additional {@link Factory} for your
53  * own views, you can use {@link #cloneInContext} to clone an existing
54  * ViewFactory, and then call {@link #setFactory} on it to include your
55  * Factory.
56  *
57  * <p>
58  * For performance reasons, view inflation relies heavily on pre-processing of
59  * XML files that is done at build time. Therefore, it is not currently possible
60  * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
61  * it only works with an XmlPullParser returned from a compiled resource
62  * (R.<em>something</em> file.)
63  *
64  * @see Context#getSystemService
65  */
66 public abstract class LayoutInflater {
67     private static final String TAG = LayoutInflater.class.getSimpleName();
68     private static final boolean DEBUG = false;
69 
70     /**
71      * This field should be made private, so it is hidden from the SDK.
72      * {@hide}
73      */
74     protected final Context mContext;
75 
76     // these are optional, set by the caller
77     private boolean mFactorySet;
78     private Factory mFactory;
79     private Factory2 mFactory2;
80     private Factory2 mPrivateFactory;
81     private Filter mFilter;
82 
83     final Object[] mConstructorArgs = new Object[2];
84 
85     static final Class<?>[] mConstructorSignature = new Class[] {
86             Context.class, AttributeSet.class};
87 
88     private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
89             new HashMap<String, Constructor<? extends View>>();
90 
91     private HashMap<String, Boolean> mFilterMap;
92 
93     private static final String TAG_MERGE = "merge";
94     private static final String TAG_INCLUDE = "include";
95     private static final String TAG_1995 = "blink";
96     private static final String TAG_REQUEST_FOCUS = "requestFocus";
97     private static final String TAG_TAG = "tag";
98 
99     private static final int[] ATTRS_THEME = new int[] {
100             com.android.internal.R.attr.theme };
101 
102     /**
103      * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
104      * to be inflated.
105      *
106      */
107     public interface Filter {
108         /**
109          * Hook to allow clients of the LayoutInflater to restrict the set of Views
110          * that are allowed to be inflated.
111          *
112          * @param clazz The class object for the View that is about to be inflated
113          *
114          * @return True if this class is allowed to be inflated, or false otherwise
115          */
116         @SuppressWarnings("unchecked")
onLoadClass(Class clazz)117         boolean onLoadClass(Class clazz);
118     }
119 
120     public interface Factory {
121         /**
122          * Hook you can supply that is called when inflating from a LayoutInflater.
123          * You can use this to customize the tag names available in your XML
124          * layout files.
125          *
126          * <p>
127          * Note that it is good practice to prefix these custom names with your
128          * package (i.e., com.coolcompany.apps) to avoid conflicts with system
129          * names.
130          *
131          * @param name Tag name to be inflated.
132          * @param context The context the view is being created in.
133          * @param attrs Inflation attributes as specified in XML file.
134          *
135          * @return View Newly created view. Return null for the default
136          *         behavior.
137          */
onCreateView(String name, Context context, AttributeSet attrs)138         public View onCreateView(String name, Context context, AttributeSet attrs);
139     }
140 
141     public interface Factory2 extends Factory {
142         /**
143          * Version of {@link #onCreateView(String, Context, AttributeSet)}
144          * that also supplies the parent that the view created view will be
145          * placed in.
146          *
147          * @param parent The parent that the created view will be placed
148          * in; <em>note that this may be null</em>.
149          * @param name Tag name to be inflated.
150          * @param context The context the view is being created in.
151          * @param attrs Inflation attributes as specified in XML file.
152          *
153          * @return View Newly created view. Return null for the default
154          *         behavior.
155          */
onCreateView(View parent, String name, Context context, AttributeSet attrs)156         public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
157     }
158 
159     private static class FactoryMerger implements Factory2 {
160         private final Factory mF1, mF2;
161         private final Factory2 mF12, mF22;
162 
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22)163         FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
164             mF1 = f1;
165             mF2 = f2;
166             mF12 = f12;
167             mF22 = f22;
168         }
169 
onCreateView(String name, Context context, AttributeSet attrs)170         public View onCreateView(String name, Context context, AttributeSet attrs) {
171             View v = mF1.onCreateView(name, context, attrs);
172             if (v != null) return v;
173             return mF2.onCreateView(name, context, attrs);
174         }
175 
onCreateView(View parent, String name, Context context, AttributeSet attrs)176         public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
177             View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
178                     : mF1.onCreateView(name, context, attrs);
179             if (v != null) return v;
180             return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
181                     : mF2.onCreateView(name, context, attrs);
182         }
183     }
184 
185     /**
186      * Create a new LayoutInflater instance associated with a particular Context.
187      * Applications will almost always want to use
188      * {@link Context#getSystemService Context.getSystemService()} to retrieve
189      * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
190      *
191      * @param context The Context in which this LayoutInflater will create its
192      * Views; most importantly, this supplies the theme from which the default
193      * values for their attributes are retrieved.
194      */
LayoutInflater(Context context)195     protected LayoutInflater(Context context) {
196         mContext = context;
197     }
198 
199     /**
200      * Create a new LayoutInflater instance that is a copy of an existing
201      * LayoutInflater, optionally with its Context changed.  For use in
202      * implementing {@link #cloneInContext}.
203      *
204      * @param original The original LayoutInflater to copy.
205      * @param newContext The new Context to use.
206      */
LayoutInflater(LayoutInflater original, Context newContext)207     protected LayoutInflater(LayoutInflater original, Context newContext) {
208         mContext = newContext;
209         mFactory = original.mFactory;
210         mFactory2 = original.mFactory2;
211         mPrivateFactory = original.mPrivateFactory;
212         setFilter(original.mFilter);
213     }
214 
215     /**
216      * Obtains the LayoutInflater from the given context.
217      */
from(Context context)218     public static LayoutInflater from(Context context) {
219         LayoutInflater LayoutInflater =
220                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
221         if (LayoutInflater == null) {
222             throw new AssertionError("LayoutInflater not found.");
223         }
224         return LayoutInflater;
225     }
226 
227     /**
228      * Create a copy of the existing LayoutInflater object, with the copy
229      * pointing to a different Context than the original.  This is used by
230      * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
231      * with the new Context theme.
232      *
233      * @param newContext The new Context to associate with the new LayoutInflater.
234      * May be the same as the original Context if desired.
235      *
236      * @return Returns a brand spanking new LayoutInflater object associated with
237      * the given Context.
238      */
cloneInContext(Context newContext)239     public abstract LayoutInflater cloneInContext(Context newContext);
240 
241     /**
242      * Return the context we are running in, for access to resources, class
243      * loader, etc.
244      */
getContext()245     public Context getContext() {
246         return mContext;
247     }
248 
249     /**
250      * Return the current {@link Factory} (or null). This is called on each element
251      * name. If the factory returns a View, add that to the hierarchy. If it
252      * returns null, proceed to call onCreateView(name).
253      */
getFactory()254     public final Factory getFactory() {
255         return mFactory;
256     }
257 
258     /**
259      * Return the current {@link Factory2}.  Returns null if no factory is set
260      * or the set factory does not implement the {@link Factory2} interface.
261      * This is called on each element
262      * name. If the factory returns a View, add that to the hierarchy. If it
263      * returns null, proceed to call onCreateView(name).
264      */
getFactory2()265     public final Factory2 getFactory2() {
266         return mFactory2;
267     }
268 
269     /**
270      * Attach a custom Factory interface for creating views while using
271      * this LayoutInflater.  This must not be null, and can only be set once;
272      * after setting, you can not change the factory.  This is
273      * called on each element name as the xml is parsed. If the factory returns
274      * a View, that is added to the hierarchy. If it returns null, the next
275      * factory default {@link #onCreateView} method is called.
276      *
277      * <p>If you have an existing
278      * LayoutInflater and want to add your own factory to it, use
279      * {@link #cloneInContext} to clone the existing instance and then you
280      * can use this function (once) on the returned new instance.  This will
281      * merge your own factory with whatever factory the original instance is
282      * using.
283      */
setFactory(Factory factory)284     public void setFactory(Factory factory) {
285         if (mFactorySet) {
286             throw new IllegalStateException("A factory has already been set on this LayoutInflater");
287         }
288         if (factory == null) {
289             throw new NullPointerException("Given factory can not be null");
290         }
291         mFactorySet = true;
292         if (mFactory == null) {
293             mFactory = factory;
294         } else {
295             mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
296         }
297     }
298 
299     /**
300      * Like {@link #setFactory}, but allows you to set a {@link Factory2}
301      * interface.
302      */
setFactory2(Factory2 factory)303     public void setFactory2(Factory2 factory) {
304         if (mFactorySet) {
305             throw new IllegalStateException("A factory has already been set on this LayoutInflater");
306         }
307         if (factory == null) {
308             throw new NullPointerException("Given factory can not be null");
309         }
310         mFactorySet = true;
311         if (mFactory == null) {
312             mFactory = mFactory2 = factory;
313         } else {
314             mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
315         }
316     }
317 
318     /**
319      * @hide for use by framework
320      */
setPrivateFactory(Factory2 factory)321     public void setPrivateFactory(Factory2 factory) {
322         if (mPrivateFactory == null) {
323             mPrivateFactory = factory;
324         } else {
325             mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
326         }
327     }
328 
329     /**
330      * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
331      * that are allowed to be inflated.
332      */
getFilter()333     public Filter getFilter() {
334         return mFilter;
335     }
336 
337     /**
338      * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
339      * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
340      * throw an {@link InflateException}. This filter will replace any previous filter set on this
341      * LayoutInflater.
342      *
343      * @param filter The Filter which restricts the set of Views that are allowed to be inflated.
344      *        This filter will replace any previous filter set on this LayoutInflater.
345      */
setFilter(Filter filter)346     public void setFilter(Filter filter) {
347         mFilter = filter;
348         if (filter != null) {
349             mFilterMap = new HashMap<String, Boolean>();
350         }
351     }
352 
353     /**
354      * Inflate a new view hierarchy from the specified xml resource. Throws
355      * {@link InflateException} if there is an error.
356      *
357      * @param resource ID for an XML layout resource to load (e.g.,
358      *        <code>R.layout.main_page</code>)
359      * @param root Optional view to be the parent of the generated hierarchy.
360      * @return The root View of the inflated hierarchy. If root was supplied,
361      *         this is the root View; otherwise it is the root of the inflated
362      *         XML file.
363      */
inflate(int resource, ViewGroup root)364     public View inflate(int resource, ViewGroup root) {
365         return inflate(resource, root, root != null);
366     }
367 
368     /**
369      * Inflate a new view hierarchy from the specified xml node. Throws
370      * {@link InflateException} if there is an error. *
371      * <p>
372      * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
373      * reasons, view inflation relies heavily on pre-processing of XML files
374      * that is done at build time. Therefore, it is not currently possible to
375      * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
376      *
377      * @param parser XML dom node containing the description of the view
378      *        hierarchy.
379      * @param root Optional view to be the parent of the generated hierarchy.
380      * @return The root View of the inflated hierarchy. If root was supplied,
381      *         this is the root View; otherwise it is the root of the inflated
382      *         XML file.
383      */
inflate(XmlPullParser parser, ViewGroup root)384     public View inflate(XmlPullParser parser, ViewGroup root) {
385         return inflate(parser, root, root != null);
386     }
387 
388     /**
389      * Inflate a new view hierarchy from the specified xml resource. Throws
390      * {@link InflateException} if there is an error.
391      *
392      * @param resource ID for an XML layout resource to load (e.g.,
393      *        <code>R.layout.main_page</code>)
394      * @param root Optional view to be the parent of the generated hierarchy (if
395      *        <em>attachToRoot</em> is true), or else simply an object that
396      *        provides a set of LayoutParams values for root of the returned
397      *        hierarchy (if <em>attachToRoot</em> is false.)
398      * @param attachToRoot Whether the inflated hierarchy should be attached to
399      *        the root parameter? If false, root is only used to create the
400      *        correct subclass of LayoutParams for the root view in the XML.
401      * @return The root View of the inflated hierarchy. If root was supplied and
402      *         attachToRoot is true, this is root; otherwise it is the root of
403      *         the inflated XML file.
404      */
inflate(int resource, ViewGroup root, boolean attachToRoot)405     public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
406         final Resources res = getContext().getResources();
407         if (DEBUG) {
408             Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
409                     + Integer.toHexString(resource) + ")");
410         }
411 
412         final XmlResourceParser parser = res.getLayout(resource);
413         try {
414             return inflate(parser, root, attachToRoot);
415         } finally {
416             parser.close();
417         }
418     }
419 
420     /**
421      * Inflate a new view hierarchy from the specified XML node. Throws
422      * {@link InflateException} if there is an error.
423      * <p>
424      * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
425      * reasons, view inflation relies heavily on pre-processing of XML files
426      * that is done at build time. Therefore, it is not currently possible to
427      * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
428      *
429      * @param parser XML dom node containing the description of the view
430      *        hierarchy.
431      * @param root Optional view to be the parent of the generated hierarchy (if
432      *        <em>attachToRoot</em> is true), or else simply an object that
433      *        provides a set of LayoutParams values for root of the returned
434      *        hierarchy (if <em>attachToRoot</em> is false.)
435      * @param attachToRoot Whether the inflated hierarchy should be attached to
436      *        the root parameter? If false, root is only used to create the
437      *        correct subclass of LayoutParams for the root view in the XML.
438      * @return The root View of the inflated hierarchy. If root was supplied and
439      *         attachToRoot is true, this is root; otherwise it is the root of
440      *         the inflated XML file.
441      */
inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)442     public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
443         synchronized (mConstructorArgs) {
444             Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
445 
446             final AttributeSet attrs = Xml.asAttributeSet(parser);
447             Context lastContext = (Context)mConstructorArgs[0];
448             mConstructorArgs[0] = mContext;
449             View result = root;
450 
451             try {
452                 // Look for the root node.
453                 int type;
454                 while ((type = parser.next()) != XmlPullParser.START_TAG &&
455                         type != XmlPullParser.END_DOCUMENT) {
456                     // Empty
457                 }
458 
459                 if (type != XmlPullParser.START_TAG) {
460                     throw new InflateException(parser.getPositionDescription()
461                             + ": No start tag found!");
462                 }
463 
464                 final String name = parser.getName();
465 
466                 if (DEBUG) {
467                     System.out.println("**************************");
468                     System.out.println("Creating root view: "
469                             + name);
470                     System.out.println("**************************");
471                 }
472 
473                 if (TAG_MERGE.equals(name)) {
474                     if (root == null || !attachToRoot) {
475                         throw new InflateException("<merge /> can be used only with a valid "
476                                 + "ViewGroup root and attachToRoot=true");
477                     }
478 
479                     rInflate(parser, root, attrs, false, false);
480                 } else {
481                     // Temp is the root view that was found in the xml
482                     final View temp = createViewFromTag(root, name, attrs, false);
483 
484                     ViewGroup.LayoutParams params = null;
485 
486                     if (root != null) {
487                         if (DEBUG) {
488                             System.out.println("Creating params from root: " +
489                                     root);
490                         }
491                         // Create layout params that match root, if supplied
492                         params = root.generateLayoutParams(attrs);
493                         if (!attachToRoot) {
494                             // Set the layout params for temp if we are not
495                             // attaching. (If we are, we use addView, below)
496                             temp.setLayoutParams(params);
497                         }
498                     }
499 
500                     if (DEBUG) {
501                         System.out.println("-----> start inflating children");
502                     }
503                     // Inflate all children under temp
504                     rInflate(parser, temp, attrs, true, true);
505                     if (DEBUG) {
506                         System.out.println("-----> done inflating children");
507                     }
508 
509                     // We are supposed to attach all the views we found (int temp)
510                     // to root. Do that now.
511                     if (root != null && attachToRoot) {
512                         root.addView(temp, params);
513                     }
514 
515                     // Decide whether to return the root that was passed in or the
516                     // top view found in xml.
517                     if (root == null || !attachToRoot) {
518                         result = temp;
519                     }
520                 }
521 
522             } catch (XmlPullParserException e) {
523                 InflateException ex = new InflateException(e.getMessage());
524                 ex.initCause(e);
525                 throw ex;
526             } catch (IOException e) {
527                 InflateException ex = new InflateException(
528                         parser.getPositionDescription()
529                         + ": " + e.getMessage());
530                 ex.initCause(e);
531                 throw ex;
532             } finally {
533                 // Don't retain static reference on context.
534                 mConstructorArgs[0] = lastContext;
535                 mConstructorArgs[1] = null;
536             }
537 
538             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
539 
540             return result;
541         }
542     }
543 
544     /**
545      * Low-level function for instantiating a view by name. This attempts to
546      * instantiate a view class of the given <var>name</var> found in this
547      * LayoutInflater's ClassLoader.
548      *
549      * <p>
550      * There are two things that can happen in an error case: either the
551      * exception describing the error will be thrown, or a null will be
552      * returned. You must deal with both possibilities -- the former will happen
553      * the first time createView() is called for a class of a particular name,
554      * the latter every time there-after for that class name.
555      *
556      * @param name The full name of the class to be instantiated.
557      * @param attrs The XML attributes supplied for this instance.
558      *
559      * @return View The newly instantiated view, or null.
560      */
createView(String name, String prefix, AttributeSet attrs)561     public final View createView(String name, String prefix, AttributeSet attrs)
562             throws ClassNotFoundException, InflateException {
563         Constructor<? extends View> constructor = sConstructorMap.get(name);
564         Class<? extends View> clazz = null;
565 
566         try {
567             Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
568 
569             if (constructor == null) {
570                 // Class not found in the cache, see if it's real, and try to add it
571                 clazz = mContext.getClassLoader().loadClass(
572                         prefix != null ? (prefix + name) : name).asSubclass(View.class);
573 
574                 if (mFilter != null && clazz != null) {
575                     boolean allowed = mFilter.onLoadClass(clazz);
576                     if (!allowed) {
577                         failNotAllowed(name, prefix, attrs);
578                     }
579                 }
580                 constructor = clazz.getConstructor(mConstructorSignature);
581                 sConstructorMap.put(name, constructor);
582             } else {
583                 // If we have a filter, apply it to cached constructor
584                 if (mFilter != null) {
585                     // Have we seen this name before?
586                     Boolean allowedState = mFilterMap.get(name);
587                     if (allowedState == null) {
588                         // New class -- remember whether it is allowed
589                         clazz = mContext.getClassLoader().loadClass(
590                                 prefix != null ? (prefix + name) : name).asSubclass(View.class);
591 
592                         boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
593                         mFilterMap.put(name, allowed);
594                         if (!allowed) {
595                             failNotAllowed(name, prefix, attrs);
596                         }
597                     } else if (allowedState.equals(Boolean.FALSE)) {
598                         failNotAllowed(name, prefix, attrs);
599                     }
600                 }
601             }
602 
603             Object[] args = mConstructorArgs;
604             args[1] = attrs;
605 
606             constructor.setAccessible(true);
607             final View view = constructor.newInstance(args);
608             if (view instanceof ViewStub) {
609                 // Use the same context when inflating ViewStub later.
610                 final ViewStub viewStub = (ViewStub) view;
611                 viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
612             }
613             return view;
614 
615         } catch (NoSuchMethodException e) {
616             InflateException ie = new InflateException(attrs.getPositionDescription()
617                     + ": Error inflating class "
618                     + (prefix != null ? (prefix + name) : name));
619             ie.initCause(e);
620             throw ie;
621 
622         } catch (ClassCastException e) {
623             // If loaded class is not a View subclass
624             InflateException ie = new InflateException(attrs.getPositionDescription()
625                     + ": Class is not a View "
626                     + (prefix != null ? (prefix + name) : name));
627             ie.initCause(e);
628             throw ie;
629         } catch (ClassNotFoundException e) {
630             // If loadClass fails, we should propagate the exception.
631             throw e;
632         } catch (Exception e) {
633             InflateException ie = new InflateException(attrs.getPositionDescription()
634                     + ": Error inflating class "
635                     + (clazz == null ? "<unknown>" : clazz.getName()));
636             ie.initCause(e);
637             throw ie;
638         } finally {
639             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
640         }
641     }
642 
643     /**
644      * Throw an exception because the specified class is not allowed to be inflated.
645      */
failNotAllowed(String name, String prefix, AttributeSet attrs)646     private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
647         throw new InflateException(attrs.getPositionDescription()
648                 + ": Class not allowed to be inflated "
649                 + (prefix != null ? (prefix + name) : name));
650     }
651 
652     /**
653      * This routine is responsible for creating the correct subclass of View
654      * given the xml element name. Override it to handle custom view objects. If
655      * you override this in your subclass be sure to call through to
656      * super.onCreateView(name) for names you do not recognize.
657      *
658      * @param name The fully qualified class name of the View to be create.
659      * @param attrs An AttributeSet of attributes to apply to the View.
660      *
661      * @return View The View created.
662      */
onCreateView(String name, AttributeSet attrs)663     protected View onCreateView(String name, AttributeSet attrs)
664             throws ClassNotFoundException {
665         return createView(name, "android.view.", attrs);
666     }
667 
668     /**
669      * Version of {@link #onCreateView(String, AttributeSet)} that also
670      * takes the future parent of the view being constructed.  The default
671      * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
672      *
673      * @param parent The future parent of the returned view.  <em>Note that
674      * this may be null.</em>
675      * @param name The fully qualified class name of the View to be create.
676      * @param attrs An AttributeSet of attributes to apply to the View.
677      *
678      * @return View The View created.
679      */
onCreateView(View parent, String name, AttributeSet attrs)680     protected View onCreateView(View parent, String name, AttributeSet attrs)
681             throws ClassNotFoundException {
682         return onCreateView(name, attrs);
683     }
684 
685     /**
686      * Creates a view from a tag name using the supplied attribute set.
687      * <p>
688      * If {@code inheritContext} is true and the parent is non-null, the view
689      * will be inflated in parent view's context. If the view specifies a
690      * &lt;theme&gt; attribute, the inflation context will be wrapped with the
691      * specified theme.
692      * <p>
693      * Note: Default visibility so the BridgeInflater can override it.
694      */
createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext)695     View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) {
696         if (name.equals("view")) {
697             name = attrs.getAttributeValue(null, "class");
698         }
699 
700         Context viewContext;
701         if (parent != null && inheritContext) {
702             viewContext = parent.getContext();
703         } else {
704             viewContext = mContext;
705         }
706 
707         // Apply a theme wrapper, if requested.
708         final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME);
709         final int themeResId = ta.getResourceId(0, 0);
710         if (themeResId != 0) {
711             viewContext = new ContextThemeWrapper(viewContext, themeResId);
712         }
713         ta.recycle();
714 
715         if (name.equals(TAG_1995)) {
716             // Let's party like it's 1995!
717             return new BlinkLayout(viewContext, attrs);
718         }
719 
720         if (DEBUG) System.out.println("******** Creating view: " + name);
721 
722         try {
723             View view;
724             if (mFactory2 != null) {
725                 view = mFactory2.onCreateView(parent, name, viewContext, attrs);
726             } else if (mFactory != null) {
727                 view = mFactory.onCreateView(name, viewContext, attrs);
728             } else {
729                 view = null;
730             }
731 
732             if (view == null && mPrivateFactory != null) {
733                 view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs);
734             }
735 
736             if (view == null) {
737                 final Object lastContext = mConstructorArgs[0];
738                 mConstructorArgs[0] = viewContext;
739                 try {
740                     if (-1 == name.indexOf('.')) {
741                         view = onCreateView(parent, name, attrs);
742                     } else {
743                         view = createView(name, null, attrs);
744                     }
745                 } finally {
746                     mConstructorArgs[0] = lastContext;
747                 }
748             }
749 
750             if (DEBUG) System.out.println("Created view is: " + view);
751             return view;
752 
753         } catch (InflateException e) {
754             throw e;
755 
756         } catch (ClassNotFoundException e) {
757             InflateException ie = new InflateException(attrs.getPositionDescription()
758                     + ": Error inflating class " + name);
759             ie.initCause(e);
760             throw ie;
761 
762         } catch (Exception e) {
763             InflateException ie = new InflateException(attrs.getPositionDescription()
764                     + ": Error inflating class " + name);
765             ie.initCause(e);
766             throw ie;
767         }
768     }
769 
770     /**
771      * Recursive method used to descend down the xml hierarchy and instantiate
772      * views, instantiate their children, and then call onFinishInflate().
773      *
774      * @param inheritContext Whether the root view should be inflated in its
775      *            parent's context. This should be true when called inflating
776      *            child views recursively, or false otherwise.
777      */
rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, boolean finishInflate, boolean inheritContext)778     void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
779             boolean finishInflate, boolean inheritContext) throws XmlPullParserException,
780             IOException {
781 
782         final int depth = parser.getDepth();
783         int type;
784 
785         while (((type = parser.next()) != XmlPullParser.END_TAG ||
786                 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
787 
788             if (type != XmlPullParser.START_TAG) {
789                 continue;
790             }
791 
792             final String name = parser.getName();
793 
794             if (TAG_REQUEST_FOCUS.equals(name)) {
795                 parseRequestFocus(parser, parent);
796             } else if (TAG_TAG.equals(name)) {
797                 parseViewTag(parser, parent, attrs);
798             } else if (TAG_INCLUDE.equals(name)) {
799                 if (parser.getDepth() == 0) {
800                     throw new InflateException("<include /> cannot be the root element");
801                 }
802                 parseInclude(parser, parent, attrs, inheritContext);
803             } else if (TAG_MERGE.equals(name)) {
804                 throw new InflateException("<merge /> must be the root element");
805             } else {
806                 final View view = createViewFromTag(parent, name, attrs, inheritContext);
807                 final ViewGroup viewGroup = (ViewGroup) parent;
808                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
809                 rInflate(parser, view, attrs, true, true);
810                 viewGroup.addView(view, params);
811             }
812         }
813 
814         if (finishInflate) parent.onFinishInflate();
815     }
816 
817     /**
818      * Parses a <code>&lt;request-focus&gt;</code> element and requests focus on
819      * the containing View.
820      */
parseRequestFocus(XmlPullParser parser, View view)821     private void parseRequestFocus(XmlPullParser parser, View view)
822             throws XmlPullParserException, IOException {
823         int type;
824         view.requestFocus();
825         final int currentDepth = parser.getDepth();
826         while (((type = parser.next()) != XmlPullParser.END_TAG ||
827                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
828             // Empty
829         }
830     }
831 
832     /**
833      * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
834      * containing View.
835      */
parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)836     private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
837             throws XmlPullParserException, IOException {
838         int type;
839 
840         final TypedArray ta = mContext.obtainStyledAttributes(
841                 attrs, com.android.internal.R.styleable.ViewTag);
842         final int key = ta.getResourceId(com.android.internal.R.styleable.ViewTag_id, 0);
843         final CharSequence value = ta.getText(com.android.internal.R.styleable.ViewTag_value);
844         view.setTag(key, value);
845         ta.recycle();
846 
847         final int currentDepth = parser.getDepth();
848         while (((type = parser.next()) != XmlPullParser.END_TAG ||
849                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
850             // Empty
851         }
852     }
853 
parseInclude(XmlPullParser parser, View parent, AttributeSet attrs, boolean inheritContext)854     private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs,
855             boolean inheritContext) throws XmlPullParserException, IOException {
856         int type;
857 
858         if (parent instanceof ViewGroup) {
859             final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
860             if (layout == 0) {
861                 final String value = attrs.getAttributeValue(null, "layout");
862                 if (value == null) {
863                     throw new InflateException("You must specifiy a layout in the"
864                             + " include tag: <include layout=\"@layout/layoutID\" />");
865                 } else {
866                     throw new InflateException("You must specifiy a valid layout "
867                             + "reference. The layout ID " + value + " is not valid.");
868                 }
869             } else {
870                 final XmlResourceParser childParser =
871                         getContext().getResources().getLayout(layout);
872 
873                 try {
874                     final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
875 
876                     while ((type = childParser.next()) != XmlPullParser.START_TAG &&
877                             type != XmlPullParser.END_DOCUMENT) {
878                         // Empty.
879                     }
880 
881                     if (type != XmlPullParser.START_TAG) {
882                         throw new InflateException(childParser.getPositionDescription() +
883                                 ": No start tag found!");
884                     }
885 
886                     final String childName = childParser.getName();
887 
888                     if (TAG_MERGE.equals(childName)) {
889                         // Inflate all children.
890                         rInflate(childParser, parent, childAttrs, false, inheritContext);
891                     } else {
892                         final View view = createViewFromTag(parent, childName, childAttrs,
893                                 inheritContext);
894                         final ViewGroup group = (ViewGroup) parent;
895 
896                         // We try to load the layout params set in the <include /> tag. If
897                         // they don't exist, we will rely on the layout params set in the
898                         // included XML file.
899                         // During a layoutparams generation, a runtime exception is thrown
900                         // if either layout_width or layout_height is missing. We catch
901                         // this exception and set localParams accordingly: true means we
902                         // successfully loaded layout params from the <include /> tag,
903                         // false means we need to rely on the included layout params.
904                         ViewGroup.LayoutParams params = null;
905                         try {
906                             params = group.generateLayoutParams(attrs);
907                         } catch (RuntimeException e) {
908                             params = group.generateLayoutParams(childAttrs);
909                         } finally {
910                             if (params != null) {
911                                 view.setLayoutParams(params);
912                             }
913                         }
914 
915                         // Inflate all children.
916                         rInflate(childParser, view, childAttrs, true, true);
917 
918                         // Attempt to override the included layout's android:id with the
919                         // one set on the <include /> tag itself.
920                         TypedArray a = mContext.obtainStyledAttributes(attrs,
921                             com.android.internal.R.styleable.View, 0, 0);
922                         int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
923                         // While we're at it, let's try to override android:visibility.
924                         int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
925                         a.recycle();
926 
927                         if (id != View.NO_ID) {
928                             view.setId(id);
929                         }
930 
931                         switch (visibility) {
932                             case 0:
933                                 view.setVisibility(View.VISIBLE);
934                                 break;
935                             case 1:
936                                 view.setVisibility(View.INVISIBLE);
937                                 break;
938                             case 2:
939                                 view.setVisibility(View.GONE);
940                                 break;
941                         }
942 
943                         group.addView(view);
944                     }
945                 } finally {
946                     childParser.close();
947                 }
948             }
949         } else {
950             throw new InflateException("<include /> can only be used inside of a ViewGroup");
951         }
952 
953         final int currentDepth = parser.getDepth();
954         while (((type = parser.next()) != XmlPullParser.END_TAG ||
955                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
956             // Empty
957         }
958     }
959 
960     private static class BlinkLayout extends FrameLayout {
961         private static final int MESSAGE_BLINK = 0x42;
962         private static final int BLINK_DELAY = 500;
963 
964         private boolean mBlink;
965         private boolean mBlinkState;
966         private final Handler mHandler;
967 
BlinkLayout(Context context, AttributeSet attrs)968         public BlinkLayout(Context context, AttributeSet attrs) {
969             super(context, attrs);
970             mHandler = new Handler(new Handler.Callback() {
971                 @Override
972                 public boolean handleMessage(Message msg) {
973                     if (msg.what == MESSAGE_BLINK) {
974                         if (mBlink) {
975                             mBlinkState = !mBlinkState;
976                             makeBlink();
977                         }
978                         invalidate();
979                         return true;
980                     }
981                     return false;
982                 }
983             });
984         }
985 
makeBlink()986         private void makeBlink() {
987             Message message = mHandler.obtainMessage(MESSAGE_BLINK);
988             mHandler.sendMessageDelayed(message, BLINK_DELAY);
989         }
990 
991         @Override
onAttachedToWindow()992         protected void onAttachedToWindow() {
993             super.onAttachedToWindow();
994 
995             mBlink = true;
996             mBlinkState = true;
997 
998             makeBlink();
999         }
1000 
1001         @Override
onDetachedFromWindow()1002         protected void onDetachedFromWindow() {
1003             super.onDetachedFromWindow();
1004 
1005             mBlink = false;
1006             mBlinkState = true;
1007 
1008             mHandler.removeMessages(MESSAGE_BLINK);
1009         }
1010 
1011         @Override
dispatchDraw(Canvas canvas)1012         protected void dispatchDraw(Canvas canvas) {
1013             if (mBlinkState) {
1014                 super.dispatchDraw(canvas);
1015             }
1016         }
1017     }
1018 }
1019