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