Home | History | Annotate | Download | only in view
      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.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.content.res.XmlResourceParser;
     22 import android.util.AttributeSet;
     23 import android.util.Xml;
     24 
     25 import org.xmlpull.v1.XmlPullParser;
     26 import org.xmlpull.v1.XmlPullParserException;
     27 
     28 import java.io.IOException;
     29 import java.lang.reflect.Constructor;
     30 import java.util.HashMap;
     31 
     32 /**
     33  * This class is used to instantiate layout XML file into its corresponding View
     34  * objects. It is never be used directly -- use
     35  * {@link android.app.Activity#getLayoutInflater()} or
     36  * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
     37  * that is already hooked up to the current context and correctly configured
     38  * for the device you are running on.  For example:
     39  *
     40  * <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
     41  *      Context.LAYOUT_INFLATER_SERVICE);</pre>
     42  *
     43  * <p>
     44  * To create a new LayoutInflater with an additional {@link Factory} for your
     45  * own views, you can use {@link #cloneInContext} to clone an existing
     46  * ViewFactory, and then call {@link #setFactory} on it to include your
     47  * Factory.
     48  *
     49  * <p>
     50  * For performance reasons, view inflation relies heavily on pre-processing of
     51  * XML files that is done at build time. Therefore, it is not currently possible
     52  * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
     53  * it only works with an XmlPullParser returned from a compiled resource
     54  * (R.<em>something</em> file.)
     55  *
     56  * @see Context#getSystemService
     57  */
     58 public abstract class LayoutInflater {
     59     private final boolean DEBUG = false;
     60 
     61     /**
     62      * This field should be made private, so it is hidden from the SDK.
     63      * {@hide}
     64      */
     65     protected final Context mContext;
     66 
     67     // these are optional, set by the caller
     68     private boolean mFactorySet;
     69     private Factory mFactory;
     70     private Filter mFilter;
     71 
     72     private final Object[] mConstructorArgs = new Object[2];
     73 
     74     private static final Class[] mConstructorSignature = new Class[] {
     75             Context.class, AttributeSet.class};
     76 
     77     private static final HashMap<String, Constructor> sConstructorMap =
     78             new HashMap<String, Constructor>();
     79 
     80     private HashMap<String, Boolean> mFilterMap;
     81 
     82     private static final String TAG_MERGE = "merge";
     83     private static final String TAG_INCLUDE = "include";
     84     private static final String TAG_REQUEST_FOCUS = "requestFocus";
     85 
     86     /**
     87      * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
     88      * to be inflated.
     89      *
     90      */
     91     public interface Filter {
     92         /**
     93          * Hook to allow clients of the LayoutInflater to restrict the set of Views
     94          * that are allowed to be inflated.
     95          *
     96          * @param clazz The class object for the View that is about to be inflated
     97          *
     98          * @return True if this class is allowed to be inflated, or false otherwise
     99          */
    100         boolean onLoadClass(Class clazz);
    101     }
    102 
    103     public interface Factory {
    104         /**
    105          * Hook you can supply that is called when inflating from a LayoutInflater.
    106          * You can use this to customize the tag names available in your XML
    107          * layout files.
    108          *
    109          * <p>
    110          * Note that it is good practice to prefix these custom names with your
    111          * package (i.e., com.coolcompany.apps) to avoid conflicts with system
    112          * names.
    113          *
    114          * @param name Tag name to be inflated.
    115          * @param context The context the view is being created in.
    116          * @param attrs Inflation attributes as specified in XML file.
    117          *
    118          * @return View Newly created view. Return null for the default
    119          *         behavior.
    120          */
    121         public View onCreateView(String name, Context context, AttributeSet attrs);
    122     }
    123 
    124     private static class FactoryMerger implements Factory {
    125         private final Factory mF1, mF2;
    126 
    127         FactoryMerger(Factory f1, Factory f2) {
    128             mF1 = f1;
    129             mF2 = f2;
    130         }
    131 
    132         public View onCreateView(String name, Context context, AttributeSet attrs) {
    133             View v = mF1.onCreateView(name, context, attrs);
    134             if (v != null) return v;
    135             return mF2.onCreateView(name, context, attrs);
    136         }
    137     }
    138 
    139     /**
    140      * Create a new LayoutInflater instance associated with a particular Context.
    141      * Applications will almost always want to use
    142      * {@link Context#getSystemService Context.getSystemService()} to retrieve
    143      * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
    144      *
    145      * @param context The Context in which this LayoutInflater will create its
    146      * Views; most importantly, this supplies the theme from which the default
    147      * values for their attributes are retrieved.
    148      */
    149     protected LayoutInflater(Context context) {
    150         mContext = context;
    151     }
    152 
    153     /**
    154      * Create a new LayoutInflater instance that is a copy of an existing
    155      * LayoutInflater, optionally with its Context changed.  For use in
    156      * implementing {@link #cloneInContext}.
    157      *
    158      * @param original The original LayoutInflater to copy.
    159      * @param newContext The new Context to use.
    160      */
    161     protected LayoutInflater(LayoutInflater original, Context newContext) {
    162         mContext = newContext;
    163         mFactory = original.mFactory;
    164         mFilter = original.mFilter;
    165     }
    166 
    167     /**
    168      * Obtains the LayoutInflater from the given context.
    169      */
    170     public static LayoutInflater from(Context context) {
    171         LayoutInflater LayoutInflater =
    172                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    173         if (LayoutInflater == null) {
    174             throw new AssertionError("LayoutInflater not found.");
    175         }
    176         return LayoutInflater;
    177     }
    178 
    179     /**
    180      * Create a copy of the existing LayoutInflater object, with the copy
    181      * pointing to a different Context than the original.  This is used by
    182      * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
    183      * with the new Context theme.
    184      *
    185      * @param newContext The new Context to associate with the new LayoutInflater.
    186      * May be the same as the original Context if desired.
    187      *
    188      * @return Returns a brand spanking new LayoutInflater object associated with
    189      * the given Context.
    190      */
    191     public abstract LayoutInflater cloneInContext(Context newContext);
    192 
    193     /**
    194      * Return the context we are running in, for access to resources, class
    195      * loader, etc.
    196      */
    197     public Context getContext() {
    198         return mContext;
    199     }
    200 
    201     /**
    202      * Return the current factory (or null). This is called on each element
    203      * name. If the factory returns a View, add that to the hierarchy. If it
    204      * returns null, proceed to call onCreateView(name).
    205      */
    206     public final Factory getFactory() {
    207         return mFactory;
    208     }
    209 
    210     /**
    211      * Attach a custom Factory interface for creating views while using
    212      * this LayoutInflater.  This must not be null, and can only be set once;
    213      * after setting, you can not change the factory.  This is
    214      * called on each element name as the xml is parsed. If the factory returns
    215      * a View, that is added to the hierarchy. If it returns null, the next
    216      * factory default {@link #onCreateView} method is called.
    217      *
    218      * <p>If you have an existing
    219      * LayoutInflater and want to add your own factory to it, use
    220      * {@link #cloneInContext} to clone the existing instance and then you
    221      * can use this function (once) on the returned new instance.  This will
    222      * merge your own factory with whatever factory the original instance is
    223      * using.
    224      */
    225     public void setFactory(Factory factory) {
    226         if (mFactorySet) {
    227             throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    228         }
    229         if (factory == null) {
    230             throw new NullPointerException("Given factory can not be null");
    231         }
    232         mFactorySet = true;
    233         if (mFactory == null) {
    234             mFactory = factory;
    235         } else {
    236             mFactory = new FactoryMerger(factory, mFactory);
    237         }
    238     }
    239 
    240     /**
    241      * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
    242      * that are allowed to be inflated.
    243      */
    244     public Filter getFilter() {
    245         return mFilter;
    246     }
    247 
    248     /**
    249      * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
    250      * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
    251      * throw an {@link InflateException}. This filter will replace any previous filter set on this
    252      * LayoutInflater.
    253      *
    254      * @param filter The Filter which restricts the set of Views that are allowed to be inflated.
    255      *        This filter will replace any previous filter set on this LayoutInflater.
    256      */
    257     public void setFilter(Filter filter) {
    258         mFilter = filter;
    259         if (filter != null) {
    260             mFilterMap = new HashMap<String, Boolean>();
    261         }
    262     }
    263 
    264     /**
    265      * Inflate a new view hierarchy from the specified xml resource. Throws
    266      * {@link InflateException} if there is an error.
    267      *
    268      * @param resource ID for an XML layout resource to load (e.g.,
    269      *        <code>R.layout.main_page</code>)
    270      * @param root Optional view to be the parent of the generated hierarchy.
    271      * @return The root View of the inflated hierarchy. If root was supplied,
    272      *         this is the root View; otherwise it is the root of the inflated
    273      *         XML file.
    274      */
    275     public View inflate(int resource, ViewGroup root) {
    276         return inflate(resource, root, root != null);
    277     }
    278 
    279     /**
    280      * Inflate a new view hierarchy from the specified xml node. Throws
    281      * {@link InflateException} if there is an error. *
    282      * <p>
    283      * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
    284      * reasons, view inflation relies heavily on pre-processing of XML files
    285      * that is done at build time. Therefore, it is not currently possible to
    286      * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
    287      *
    288      * @param parser XML dom node containing the description of the view
    289      *        hierarchy.
    290      * @param root Optional view to be the parent of the generated hierarchy.
    291      * @return The root View of the inflated hierarchy. If root was supplied,
    292      *         this is the root View; otherwise it is the root of the inflated
    293      *         XML file.
    294      */
    295     public View inflate(XmlPullParser parser, ViewGroup root) {
    296         return inflate(parser, root, root != null);
    297     }
    298 
    299     /**
    300      * Inflate a new view hierarchy from the specified xml resource. Throws
    301      * {@link InflateException} if there is an error.
    302      *
    303      * @param resource ID for an XML layout resource to load (e.g.,
    304      *        <code>R.layout.main_page</code>)
    305      * @param root Optional view to be the parent of the generated hierarchy (if
    306      *        <em>attachToRoot</em> is true), or else simply an object that
    307      *        provides a set of LayoutParams values for root of the returned
    308      *        hierarchy (if <em>attachToRoot</em> is false.)
    309      * @param attachToRoot Whether the inflated hierarchy should be attached to
    310      *        the root parameter? If false, root is only used to create the
    311      *        correct subclass of LayoutParams for the root view in the XML.
    312      * @return The root View of the inflated hierarchy. If root was supplied and
    313      *         attachToRoot is true, this is root; otherwise it is the root of
    314      *         the inflated XML file.
    315      */
    316     public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
    317         if (DEBUG) System.out.println("INFLATING from resource: " + resource);
    318         XmlResourceParser parser = getContext().getResources().getLayout(resource);
    319         try {
    320             return inflate(parser, root, attachToRoot);
    321         } finally {
    322             parser.close();
    323         }
    324     }
    325 
    326     /**
    327      * Inflate a new view hierarchy from the specified XML node. Throws
    328      * {@link InflateException} if there is an error.
    329      * <p>
    330      * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
    331      * reasons, view inflation relies heavily on pre-processing of XML files
    332      * that is done at build time. Therefore, it is not currently possible to
    333      * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
    334      *
    335      * @param parser XML dom node containing the description of the view
    336      *        hierarchy.
    337      * @param root Optional view to be the parent of the generated hierarchy (if
    338      *        <em>attachToRoot</em> is true), or else simply an object that
    339      *        provides a set of LayoutParams values for root of the returned
    340      *        hierarchy (if <em>attachToRoot</em> is false.)
    341      * @param attachToRoot Whether the inflated hierarchy should be attached to
    342      *        the root parameter? If false, root is only used to create the
    343      *        correct subclass of LayoutParams for the root view in the XML.
    344      * @return The root View of the inflated hierarchy. If root was supplied and
    345      *         attachToRoot is true, this is root; otherwise it is the root of
    346      *         the inflated XML file.
    347      */
    348     public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
    349         synchronized (mConstructorArgs) {
    350             final AttributeSet attrs = Xml.asAttributeSet(parser);
    351             Context lastContext = (Context)mConstructorArgs[0];
    352             mConstructorArgs[0] = mContext;
    353             View result = root;
    354 
    355             try {
    356                 // Look for the root node.
    357                 int type;
    358                 while ((type = parser.next()) != XmlPullParser.START_TAG &&
    359                         type != XmlPullParser.END_DOCUMENT) {
    360                     // Empty
    361                 }
    362 
    363                 if (type != XmlPullParser.START_TAG) {
    364                     throw new InflateException(parser.getPositionDescription()
    365                             + ": No start tag found!");
    366                 }
    367 
    368                 final String name = parser.getName();
    369 
    370                 if (DEBUG) {
    371                     System.out.println("**************************");
    372                     System.out.println("Creating root view: "
    373                             + name);
    374                     System.out.println("**************************");
    375                 }
    376 
    377                 if (TAG_MERGE.equals(name)) {
    378                     if (root == null || !attachToRoot) {
    379                         throw new InflateException("<merge /> can be used only with a valid "
    380                                 + "ViewGroup root and attachToRoot=true");
    381                     }
    382 
    383                     rInflate(parser, root, attrs);
    384                 } else {
    385                     // Temp is the root view that was found in the xml
    386                     View temp = createViewFromTag(name, attrs);
    387 
    388                     ViewGroup.LayoutParams params = null;
    389 
    390                     if (root != null) {
    391                         if (DEBUG) {
    392                             System.out.println("Creating params from root: " +
    393                                     root);
    394                         }
    395                         // Create layout params that match root, if supplied
    396                         params = root.generateLayoutParams(attrs);
    397                         if (!attachToRoot) {
    398                             // Set the layout params for temp if we are not
    399                             // attaching. (If we are, we use addView, below)
    400                             temp.setLayoutParams(params);
    401                         }
    402                     }
    403 
    404                     if (DEBUG) {
    405                         System.out.println("-----> start inflating children");
    406                     }
    407                     // Inflate all children under temp
    408                     rInflate(parser, temp, attrs);
    409                     if (DEBUG) {
    410                         System.out.println("-----> done inflating children");
    411                     }
    412 
    413                     // We are supposed to attach all the views we found (int temp)
    414                     // to root. Do that now.
    415                     if (root != null && attachToRoot) {
    416                         root.addView(temp, params);
    417                     }
    418 
    419                     // Decide whether to return the root that was passed in or the
    420                     // top view found in xml.
    421                     if (root == null || !attachToRoot) {
    422                         result = temp;
    423                     }
    424                 }
    425 
    426             } catch (XmlPullParserException e) {
    427                 InflateException ex = new InflateException(e.getMessage());
    428                 ex.initCause(e);
    429                 throw ex;
    430             } catch (IOException e) {
    431                 InflateException ex = new InflateException(
    432                         parser.getPositionDescription()
    433                         + ": " + e.getMessage());
    434                 ex.initCause(e);
    435                 throw ex;
    436             } finally {
    437                 // Don't retain static reference on context.
    438                 mConstructorArgs[0] = lastContext;
    439                 mConstructorArgs[1] = null;
    440             }
    441 
    442             return result;
    443         }
    444     }
    445 
    446     /**
    447      * Low-level function for instantiating a view by name. This attempts to
    448      * instantiate a view class of the given <var>name</var> found in this
    449      * LayoutInflater's ClassLoader.
    450      *
    451      * <p>
    452      * There are two things that can happen in an error case: either the
    453      * exception describing the error will be thrown, or a null will be
    454      * returned. You must deal with both possibilities -- the former will happen
    455      * the first time createView() is called for a class of a particular name,
    456      * the latter every time there-after for that class name.
    457      *
    458      * @param name The full name of the class to be instantiated.
    459      * @param attrs The XML attributes supplied for this instance.
    460      *
    461      * @return View The newly instantied view, or null.
    462      */
    463     public final View createView(String name, String prefix, AttributeSet attrs)
    464             throws ClassNotFoundException, InflateException {
    465         Constructor constructor = sConstructorMap.get(name);
    466         Class clazz = null;
    467 
    468         try {
    469             if (constructor == null) {
    470                 // Class not found in the cache, see if it's real, and try to add it
    471                 clazz = mContext.getClassLoader().loadClass(
    472                         prefix != null ? (prefix + name) : name);
    473 
    474                 if (mFilter != null && clazz != null) {
    475                     boolean allowed = mFilter.onLoadClass(clazz);
    476                     if (!allowed) {
    477                         failNotAllowed(name, prefix, attrs);
    478                     }
    479                 }
    480                 constructor = clazz.getConstructor(mConstructorSignature);
    481                 sConstructorMap.put(name, constructor);
    482             } else {
    483                 // If we have a filter, apply it to cached constructor
    484                 if (mFilter != null) {
    485                     // Have we seen this name before?
    486                     Boolean allowedState = mFilterMap.get(name);
    487                     if (allowedState == null) {
    488                         // New class -- remember whether it is allowed
    489                         clazz = mContext.getClassLoader().loadClass(
    490                                 prefix != null ? (prefix + name) : name);
    491 
    492                         boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
    493                         mFilterMap.put(name, allowed);
    494                         if (!allowed) {
    495                             failNotAllowed(name, prefix, attrs);
    496                         }
    497                     } else if (allowedState.equals(Boolean.FALSE)) {
    498                         failNotAllowed(name, prefix, attrs);
    499                     }
    500                 }
    501             }
    502 
    503             Object[] args = mConstructorArgs;
    504             args[1] = attrs;
    505             return (View) constructor.newInstance(args);
    506 
    507         } catch (NoSuchMethodException e) {
    508             InflateException ie = new InflateException(attrs.getPositionDescription()
    509                     + ": Error inflating class "
    510                     + (prefix != null ? (prefix + name) : name));
    511             ie.initCause(e);
    512             throw ie;
    513 
    514         } catch (ClassNotFoundException e) {
    515             // If loadClass fails, we should propagate the exception.
    516             throw e;
    517         } catch (Exception e) {
    518             InflateException ie = new InflateException(attrs.getPositionDescription()
    519                     + ": Error inflating class "
    520                     + (clazz == null ? "<unknown>" : clazz.getName()));
    521             ie.initCause(e);
    522             throw ie;
    523         }
    524     }
    525 
    526     /**
    527      * Throw an excpetion because the specified class is not allowed to be inflated.
    528      */
    529     private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
    530         InflateException ie = new InflateException(attrs.getPositionDescription()
    531                 + ": Class not allowed to be inflated "
    532                 + (prefix != null ? (prefix + name) : name));
    533         throw ie;
    534     }
    535 
    536     /**
    537      * This routine is responsible for creating the correct subclass of View
    538      * given the xml element name. Override it to handle custom view objects. If
    539      * you override this in your subclass be sure to call through to
    540      * super.onCreateView(name) for names you do not recognize.
    541      *
    542      * @param name The fully qualified class name of the View to be create.
    543      * @param attrs An AttributeSet of attributes to apply to the View.
    544      *
    545      * @return View The View created.
    546      */
    547     protected View onCreateView(String name, AttributeSet attrs)
    548             throws ClassNotFoundException {
    549         return createView(name, "android.view.", attrs);
    550     }
    551 
    552     /*
    553      * default visibility so the BridgeInflater can override it.
    554      */
    555     View createViewFromTag(String name, AttributeSet attrs) {
    556         if (name.equals("view")) {
    557             name = attrs.getAttributeValue(null, "class");
    558         }
    559 
    560         if (DEBUG) System.out.println("******** Creating view: " + name);
    561 
    562         try {
    563             View view = (mFactory == null) ? null : mFactory.onCreateView(name,
    564                     mContext, attrs);
    565 
    566             if (view == null) {
    567                 if (-1 == name.indexOf('.')) {
    568                     view = onCreateView(name, attrs);
    569                 } else {
    570                     view = createView(name, null, attrs);
    571                 }
    572             }
    573 
    574             if (DEBUG) System.out.println("Created view is: " + view);
    575             return view;
    576 
    577         } catch (InflateException e) {
    578             throw e;
    579 
    580         } catch (ClassNotFoundException e) {
    581             InflateException ie = new InflateException(attrs.getPositionDescription()
    582                     + ": Error inflating class " + name);
    583             ie.initCause(e);
    584             throw ie;
    585 
    586         } catch (Exception e) {
    587             InflateException ie = new InflateException(attrs.getPositionDescription()
    588                     + ": Error inflating class " + name);
    589             ie.initCause(e);
    590             throw ie;
    591         }
    592     }
    593 
    594     /**
    595      * Recursive method used to descend down the xml hierarchy and instantiate
    596      * views, instantiate their children, and then call onFinishInflate().
    597      */
    598     private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
    599             throws XmlPullParserException, IOException {
    600 
    601         final int depth = parser.getDepth();
    602         int type;
    603 
    604         while (((type = parser.next()) != XmlPullParser.END_TAG ||
    605                 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
    606 
    607             if (type != XmlPullParser.START_TAG) {
    608                 continue;
    609             }
    610 
    611             final String name = parser.getName();
    612 
    613             if (TAG_REQUEST_FOCUS.equals(name)) {
    614                 parseRequestFocus(parser, parent);
    615             } else if (TAG_INCLUDE.equals(name)) {
    616                 if (parser.getDepth() == 0) {
    617                     throw new InflateException("<include /> cannot be the root element");
    618                 }
    619                 parseInclude(parser, parent, attrs);
    620             } else if (TAG_MERGE.equals(name)) {
    621                 throw new InflateException("<merge /> must be the root element");
    622             } else {
    623                 final View view = createViewFromTag(name, attrs);
    624                 final ViewGroup viewGroup = (ViewGroup) parent;
    625                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
    626                 rInflate(parser, view, attrs);
    627                 viewGroup.addView(view, params);
    628             }
    629         }
    630 
    631         parent.onFinishInflate();
    632     }
    633 
    634     private void parseRequestFocus(XmlPullParser parser, View parent)
    635             throws XmlPullParserException, IOException {
    636         int type;
    637         parent.requestFocus();
    638         final int currentDepth = parser.getDepth();
    639         while (((type = parser.next()) != XmlPullParser.END_TAG ||
    640                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
    641             // Empty
    642         }
    643     }
    644 
    645     private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)
    646             throws XmlPullParserException, IOException {
    647 
    648         int type;
    649 
    650         if (parent instanceof ViewGroup) {
    651             final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
    652             if (layout == 0) {
    653                 final String value = attrs.getAttributeValue(null, "layout");
    654                 if (value == null) {
    655                     throw new InflateException("You must specifiy a layout in the"
    656                             + " include tag: <include layout=\"@layout/layoutID\" />");
    657                 } else {
    658                     throw new InflateException("You must specifiy a valid layout "
    659                             + "reference. The layout ID " + value + " is not valid.");
    660                 }
    661             } else {
    662                 final XmlResourceParser childParser =
    663                         getContext().getResources().getLayout(layout);
    664 
    665                 try {
    666                     final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
    667 
    668                     while ((type = childParser.next()) != XmlPullParser.START_TAG &&
    669                             type != XmlPullParser.END_DOCUMENT) {
    670                         // Empty.
    671                     }
    672 
    673                     if (type != XmlPullParser.START_TAG) {
    674                         throw new InflateException(childParser.getPositionDescription() +
    675                                 ": No start tag found!");
    676                     }
    677 
    678                     final String childName = childParser.getName();
    679 
    680                     if (TAG_MERGE.equals(childName)) {
    681                         // Inflate all children.
    682                         rInflate(childParser, parent, childAttrs);
    683                     } else {
    684                         final View view = createViewFromTag(childName, childAttrs);
    685                         final ViewGroup group = (ViewGroup) parent;
    686 
    687                         // We try to load the layout params set in the <include /> tag. If
    688                         // they don't exist, we will rely on the layout params set in the
    689                         // included XML file.
    690                         // During a layoutparams generation, a runtime exception is thrown
    691                         // if either layout_width or layout_height is missing. We catch
    692                         // this exception and set localParams accordingly: true means we
    693                         // successfully loaded layout params from the <include /> tag,
    694                         // false means we need to rely on the included layout params.
    695                         ViewGroup.LayoutParams params = null;
    696                         try {
    697                             params = group.generateLayoutParams(attrs);
    698                         } catch (RuntimeException e) {
    699                             params = group.generateLayoutParams(childAttrs);
    700                         } finally {
    701                             if (params != null) {
    702                                 view.setLayoutParams(params);
    703                             }
    704                         }
    705 
    706                         // Inflate all children.
    707                         rInflate(childParser, view, childAttrs);
    708 
    709                         // Attempt to override the included layout's android:id with the
    710                         // one set on the <include /> tag itself.
    711                         TypedArray a = mContext.obtainStyledAttributes(attrs,
    712                             com.android.internal.R.styleable.View, 0, 0);
    713                         int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
    714                         // While we're at it, let's try to override android:visibility.
    715                         int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
    716                         a.recycle();
    717 
    718                         if (id != View.NO_ID) {
    719                             view.setId(id);
    720                         }
    721 
    722                         switch (visibility) {
    723                             case 0:
    724                                 view.setVisibility(View.VISIBLE);
    725                                 break;
    726                             case 1:
    727                                 view.setVisibility(View.INVISIBLE);
    728                                 break;
    729                             case 2:
    730                                 view.setVisibility(View.GONE);
    731                                 break;
    732                         }
    733 
    734                         group.addView(view);
    735                     }
    736                 } finally {
    737                     childParser.close();
    738                 }
    739             }
    740         } else {
    741             throw new InflateException("<include /> can only be used inside of a ViewGroup");
    742         }
    743 
    744         final int currentDepth = parser.getDepth();
    745         while (((type = parser.next()) != XmlPullParser.END_TAG ||
    746                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
    747             // Empty
    748         }
    749     }
    750 }
    751