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