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     /**
     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                 InflateException ex = new InflateException(e.getMessage());
    536                 ex.initCause(e);
    537                 throw ex;
    538             } catch (Exception e) {
    539                 InflateException ex = new InflateException(
    540                         parser.getPositionDescription()
    541                                 + ": " + e.getMessage());
    542                 ex.initCause(e);
    543                 throw ex;
    544             } finally {
    545                 // Don't retain static reference on context.
    546                 mConstructorArgs[0] = lastContext;
    547                 mConstructorArgs[1] = null;
    548             }
    549 
    550             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    551 
    552             return result;
    553         }
    554     }
    555 
    556     /**
    557      * Low-level function for instantiating a view by name. This attempts to
    558      * instantiate a view class of the given <var>name</var> found in this
    559      * LayoutInflater's ClassLoader.
    560      *
    561      * <p>
    562      * There are two things that can happen in an error case: either the
    563      * exception describing the error will be thrown, or a null will be
    564      * returned. You must deal with both possibilities -- the former will happen
    565      * the first time createView() is called for a class of a particular name,
    566      * the latter every time there-after for that class name.
    567      *
    568      * @param name The full name of the class to be instantiated.
    569      * @param attrs The XML attributes supplied for this instance.
    570      *
    571      * @return View The newly instantiated view, or null.
    572      */
    573     public final View createView(String name, String prefix, AttributeSet attrs)
    574             throws ClassNotFoundException, InflateException {
    575         Constructor<? extends View> constructor = sConstructorMap.get(name);
    576         Class<? extends View> clazz = null;
    577 
    578         try {
    579             Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
    580 
    581             if (constructor == null) {
    582                 // Class not found in the cache, see if it's real, and try to add it
    583                 clazz = mContext.getClassLoader().loadClass(
    584                         prefix != null ? (prefix + name) : name).asSubclass(View.class);
    585 
    586                 if (mFilter != null && clazz != null) {
    587                     boolean allowed = mFilter.onLoadClass(clazz);
    588                     if (!allowed) {
    589                         failNotAllowed(name, prefix, attrs);
    590                     }
    591                 }
    592                 constructor = clazz.getConstructor(mConstructorSignature);
    593                 constructor.setAccessible(true);
    594                 sConstructorMap.put(name, constructor);
    595             } else {
    596                 // If we have a filter, apply it to cached constructor
    597                 if (mFilter != null) {
    598                     // Have we seen this name before?
    599                     Boolean allowedState = mFilterMap.get(name);
    600                     if (allowedState == null) {
    601                         // New class -- remember whether it is allowed
    602                         clazz = mContext.getClassLoader().loadClass(
    603                                 prefix != null ? (prefix + name) : name).asSubclass(View.class);
    604 
    605                         boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
    606                         mFilterMap.put(name, allowed);
    607                         if (!allowed) {
    608                             failNotAllowed(name, prefix, attrs);
    609                         }
    610                     } else if (allowedState.equals(Boolean.FALSE)) {
    611                         failNotAllowed(name, prefix, attrs);
    612                     }
    613                 }
    614             }
    615 
    616             Object[] args = mConstructorArgs;
    617             args[1] = attrs;
    618 
    619             final View view = constructor.newInstance(args);
    620             if (view instanceof ViewStub) {
    621                 // Use the same context when inflating ViewStub later.
    622                 final ViewStub viewStub = (ViewStub) view;
    623                 viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
    624             }
    625             return view;
    626 
    627         } catch (NoSuchMethodException e) {
    628             InflateException ie = new InflateException(attrs.getPositionDescription()
    629                     + ": Error inflating class "
    630                     + (prefix != null ? (prefix + name) : name));
    631             ie.initCause(e);
    632             throw ie;
    633 
    634         } catch (ClassCastException e) {
    635             // If loaded class is not a View subclass
    636             InflateException ie = new InflateException(attrs.getPositionDescription()
    637                     + ": Class is not a View "
    638                     + (prefix != null ? (prefix + name) : name));
    639             ie.initCause(e);
    640             throw ie;
    641         } catch (ClassNotFoundException e) {
    642             // If loadClass fails, we should propagate the exception.
    643             throw e;
    644         } catch (Exception e) {
    645             InflateException ie = new InflateException(attrs.getPositionDescription()
    646                     + ": Error inflating class "
    647                     + (clazz == null ? "<unknown>" : clazz.getName()));
    648             ie.initCause(e);
    649             throw ie;
    650         } finally {
    651             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    652         }
    653     }
    654 
    655     /**
    656      * Throw an exception because the specified class is not allowed to be inflated.
    657      */
    658     private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
    659         throw new InflateException(attrs.getPositionDescription()
    660                 + ": Class not allowed to be inflated "
    661                 + (prefix != null ? (prefix + name) : name));
    662     }
    663 
    664     /**
    665      * This routine is responsible for creating the correct subclass of View
    666      * given the xml element name. Override it to handle custom view objects. If
    667      * you override this in your subclass be sure to call through to
    668      * super.onCreateView(name) for names you do not recognize.
    669      *
    670      * @param name The fully qualified class name of the View to be create.
    671      * @param attrs An AttributeSet of attributes to apply to the View.
    672      *
    673      * @return View The View created.
    674      */
    675     protected View onCreateView(String name, AttributeSet attrs)
    676             throws ClassNotFoundException {
    677         return createView(name, "android.view.", attrs);
    678     }
    679 
    680     /**
    681      * Version of {@link #onCreateView(String, AttributeSet)} that also
    682      * takes the future parent of the view being constructed.  The default
    683      * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
    684      *
    685      * @param parent The future parent of the returned view.  <em>Note that
    686      * this may be null.</em>
    687      * @param name The fully qualified class name of the View to be create.
    688      * @param attrs An AttributeSet of attributes to apply to the View.
    689      *
    690      * @return View The View created.
    691      */
    692     protected View onCreateView(View parent, String name, AttributeSet attrs)
    693             throws ClassNotFoundException {
    694         return onCreateView(name, attrs);
    695     }
    696 
    697     /**
    698      * Convenience method for calling through to the five-arg createViewFromTag
    699      * method. This method passes {@code false} for the {@code ignoreThemeAttr}
    700      * argument and should be used for everything except {@code &gt;include>}
    701      * tag parsing.
    702      */
    703     private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    704         return createViewFromTag(parent, name, context, attrs, false);
    705     }
    706 
    707     /**
    708      * Creates a view from a tag name using the supplied attribute set.
    709      * <p>
    710      * <strong>Note:</strong> Default visibility so the BridgeInflater can
    711      * override it.
    712      *
    713      * @param parent the parent view, used to inflate layout params
    714      * @param name the name of the XML tag used to define the view
    715      * @param context the inflation context for the view, typically the
    716      *                {@code parent} or base layout inflater context
    717      * @param attrs the attribute set for the XML tag used to define the view
    718      * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
    719      *                        attribute (if set) for the view being inflated,
    720      *                        {@code false} otherwise
    721      */
    722     View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
    723             boolean ignoreThemeAttr) {
    724         if (name.equals("view")) {
    725             name = attrs.getAttributeValue(null, "class");
    726         }
    727 
    728         // Apply a theme wrapper, if allowed and one is specified.
    729         if (!ignoreThemeAttr) {
    730             final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
    731             final int themeResId = ta.getResourceId(0, 0);
    732             if (themeResId != 0) {
    733                 context = new ContextThemeWrapper(context, themeResId);
    734             }
    735             ta.recycle();
    736         }
    737 
    738         if (name.equals(TAG_1995)) {
    739             // Let's party like it's 1995!
    740             return new BlinkLayout(context, attrs);
    741         }
    742 
    743         try {
    744             View view;
    745             if (mFactory2 != null) {
    746                 view = mFactory2.onCreateView(parent, name, context, attrs);
    747             } else if (mFactory != null) {
    748                 view = mFactory.onCreateView(name, context, attrs);
    749             } else {
    750                 view = null;
    751             }
    752 
    753             if (view == null && mPrivateFactory != null) {
    754                 view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    755             }
    756 
    757             if (view == null) {
    758                 final Object lastContext = mConstructorArgs[0];
    759                 mConstructorArgs[0] = context;
    760                 try {
    761                     if (-1 == name.indexOf('.')) {
    762                         view = onCreateView(parent, name, attrs);
    763                     } else {
    764                         view = createView(name, null, attrs);
    765                     }
    766                 } finally {
    767                     mConstructorArgs[0] = lastContext;
    768                 }
    769             }
    770 
    771             return view;
    772         } catch (InflateException e) {
    773             throw e;
    774 
    775         } catch (ClassNotFoundException e) {
    776             final InflateException ie = new InflateException(attrs.getPositionDescription()
    777                     + ": Error inflating class " + name);
    778             ie.initCause(e);
    779             throw ie;
    780 
    781         } catch (Exception e) {
    782             final InflateException ie = new InflateException(attrs.getPositionDescription()
    783                     + ": Error inflating class " + name);
    784             ie.initCause(e);
    785             throw ie;
    786         }
    787     }
    788 
    789     /**
    790      * Recursive method used to inflate internal (non-root) children. This
    791      * method calls through to {@link #rInflate} using the parent context as
    792      * the inflation context.
    793      * <strong>Note:</strong> Default visibility so the BridgeInflater can
    794      * call it.
    795      */
    796     final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
    797             boolean finishInflate) throws XmlPullParserException, IOException {
    798         rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    799     }
    800 
    801     /**
    802      * Recursive method used to descend down the xml hierarchy and instantiate
    803      * views, instantiate their children, and then call onFinishInflate().
    804      * <p>
    805      * <strong>Note:</strong> Default visibility so the BridgeInflater can
    806      * override it.
    807      */
    808     void rInflate(XmlPullParser parser, View parent, Context context,
    809             AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    810 
    811         final int depth = parser.getDepth();
    812         int type;
    813 
    814         while (((type = parser.next()) != XmlPullParser.END_TAG ||
    815                 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
    816 
    817             if (type != XmlPullParser.START_TAG) {
    818                 continue;
    819             }
    820 
    821             final String name = parser.getName();
    822 
    823             if (TAG_REQUEST_FOCUS.equals(name)) {
    824                 parseRequestFocus(parser, parent);
    825             } else if (TAG_TAG.equals(name)) {
    826                 parseViewTag(parser, parent, attrs);
    827             } else if (TAG_INCLUDE.equals(name)) {
    828                 if (parser.getDepth() == 0) {
    829                     throw new InflateException("<include /> cannot be the root element");
    830                 }
    831                 parseInclude(parser, context, parent, attrs);
    832             } else if (TAG_MERGE.equals(name)) {
    833                 throw new InflateException("<merge /> must be the root element");
    834             } else {
    835                 final View view = createViewFromTag(parent, name, context, attrs);
    836                 final ViewGroup viewGroup = (ViewGroup) parent;
    837                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
    838                 rInflateChildren(parser, view, attrs, true);
    839                 viewGroup.addView(view, params);
    840             }
    841         }
    842 
    843         if (finishInflate) {
    844             parent.onFinishInflate();
    845         }
    846     }
    847 
    848     /**
    849      * Parses a <code>&lt;request-focus&gt;</code> element and requests focus on
    850      * the containing View.
    851      */
    852     private void parseRequestFocus(XmlPullParser parser, View view)
    853             throws XmlPullParserException, IOException {
    854         view.requestFocus();
    855 
    856         consumeChildElements(parser);
    857     }
    858 
    859     /**
    860      * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
    861      * containing View.
    862      */
    863     private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
    864             throws XmlPullParserException, IOException {
    865         final Context context = view.getContext();
    866         final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
    867         final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
    868         final CharSequence value = ta.getText(R.styleable.ViewTag_value);
    869         view.setTag(key, value);
    870         ta.recycle();
    871 
    872         consumeChildElements(parser);
    873     }
    874 
    875     private void parseInclude(XmlPullParser parser, Context context, View parent,
    876             AttributeSet attrs) throws XmlPullParserException, IOException {
    877         int type;
    878 
    879         if (parent instanceof ViewGroup) {
    880             // Apply a theme wrapper, if requested. This is sort of a weird
    881             // edge case, since developers think the <include> overwrites
    882             // values in the AttributeSet of the included View. So, if the
    883             // included View has a theme attribute, we'll need to ignore it.
    884             final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
    885             final int themeResId = ta.getResourceId(0, 0);
    886             final boolean hasThemeOverride = themeResId != 0;
    887             if (hasThemeOverride) {
    888                 context = new ContextThemeWrapper(context, themeResId);
    889             }
    890             ta.recycle();
    891 
    892             // If the layout is pointing to a theme attribute, we have to
    893             // massage the value to get a resource identifier out of it.
    894             int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
    895             if (layout == 0) {
    896                 final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
    897                 if (value == null || value.length() <= 0) {
    898                     throw new InflateException("You must specify a layout in the"
    899                             + " include tag: <include layout=\"@layout/layoutID\" />");
    900                 }
    901 
    902                 // Attempt to resolve the "?attr/name" string to an identifier.
    903                 layout = context.getResources().getIdentifier(value.substring(1), null, null);
    904             }
    905 
    906             // The layout might be referencing a theme attribute.
    907             if (mTempValue == null) {
    908                 mTempValue = new TypedValue();
    909             }
    910             if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
    911                 layout = mTempValue.resourceId;
    912             }
    913 
    914             if (layout == 0) {
    915                 final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
    916                 throw new InflateException("You must specify a valid layout "
    917                         + "reference. The layout ID " + value + " is not valid.");
    918             } else {
    919                 final XmlResourceParser childParser = context.getResources().getLayout(layout);
    920 
    921                 try {
    922                     final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
    923 
    924                     while ((type = childParser.next()) != XmlPullParser.START_TAG &&
    925                             type != XmlPullParser.END_DOCUMENT) {
    926                         // Empty.
    927                     }
    928 
    929                     if (type != XmlPullParser.START_TAG) {
    930                         throw new InflateException(childParser.getPositionDescription() +
    931                                 ": No start tag found!");
    932                     }
    933 
    934                     final String childName = childParser.getName();
    935 
    936                     if (TAG_MERGE.equals(childName)) {
    937                         // The <merge> tag doesn't support android:theme, so
    938                         // nothing special to do here.
    939                         rInflate(childParser, parent, context, childAttrs, false);
    940                     } else {
    941                         final View view = createViewFromTag(parent, childName,
    942                                 context, childAttrs, hasThemeOverride);
    943                         final ViewGroup group = (ViewGroup) parent;
    944 
    945                         final TypedArray a = context.obtainStyledAttributes(
    946                                 attrs, R.styleable.Include);
    947                         final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
    948                         final int visibility = a.getInt(R.styleable.Include_visibility, -1);
    949                         a.recycle();
    950 
    951                         // We try to load the layout params set in the <include /> tag.
    952                         // If the parent can't generate layout params (ex. missing width
    953                         // or height for the framework ViewGroups, though this is not
    954                         // necessarily true of all ViewGroups) then we expect it to throw
    955                         // a runtime exception.
    956                         // We catch this exception and set localParams accordingly: true
    957                         // means we successfully loaded layout params from the <include>
    958                         // tag, false means we need to rely on the included layout params.
    959                         ViewGroup.LayoutParams params = null;
    960                         try {
    961                             params = group.generateLayoutParams(attrs);
    962                         } catch (RuntimeException e) {
    963                             // Ignore, just fail over to child attrs.
    964                         }
    965                         if (params == null) {
    966                             params = group.generateLayoutParams(childAttrs);
    967                         }
    968                         view.setLayoutParams(params);
    969 
    970                         // Inflate all children.
    971                         rInflateChildren(childParser, view, childAttrs, true);
    972 
    973                         if (id != View.NO_ID) {
    974                             view.setId(id);
    975                         }
    976 
    977                         switch (visibility) {
    978                             case 0:
    979                                 view.setVisibility(View.VISIBLE);
    980                                 break;
    981                             case 1:
    982                                 view.setVisibility(View.INVISIBLE);
    983                                 break;
    984                             case 2:
    985                                 view.setVisibility(View.GONE);
    986                                 break;
    987                         }
    988 
    989                         group.addView(view);
    990                     }
    991                 } finally {
    992                     childParser.close();
    993                 }
    994             }
    995         } else {
    996             throw new InflateException("<include /> can only be used inside of a ViewGroup");
    997         }
    998 
    999         LayoutInflater.consumeChildElements(parser);
   1000     }
   1001 
   1002     /**
   1003      * <strong>Note:</strong> default visibility so that
   1004      * LayoutInflater_Delegate can call it.
   1005      */
   1006     final static void consumeChildElements(XmlPullParser parser)
   1007             throws XmlPullParserException, IOException {
   1008         int type;
   1009         final int currentDepth = parser.getDepth();
   1010         while (((type = parser.next()) != XmlPullParser.END_TAG ||
   1011                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
   1012             // Empty
   1013         }
   1014     }
   1015 
   1016     private static class BlinkLayout extends FrameLayout {
   1017         private static final int MESSAGE_BLINK = 0x42;
   1018         private static final int BLINK_DELAY = 500;
   1019 
   1020         private boolean mBlink;
   1021         private boolean mBlinkState;
   1022         private final Handler mHandler;
   1023 
   1024         public BlinkLayout(Context context, AttributeSet attrs) {
   1025             super(context, attrs);
   1026             mHandler = new Handler(new Handler.Callback() {
   1027                 @Override
   1028                 public boolean handleMessage(Message msg) {
   1029                     if (msg.what == MESSAGE_BLINK) {
   1030                         if (mBlink) {
   1031                             mBlinkState = !mBlinkState;
   1032                             makeBlink();
   1033                         }
   1034                         invalidate();
   1035                         return true;
   1036                     }
   1037                     return false;
   1038                 }
   1039             });
   1040         }
   1041 
   1042         private void makeBlink() {
   1043             Message message = mHandler.obtainMessage(MESSAGE_BLINK);
   1044             mHandler.sendMessageDelayed(message, BLINK_DELAY);
   1045         }
   1046 
   1047         @Override
   1048         protected void onAttachedToWindow() {
   1049             super.onAttachedToWindow();
   1050 
   1051             mBlink = true;
   1052             mBlinkState = true;
   1053 
   1054             makeBlink();
   1055         }
   1056 
   1057         @Override
   1058         protected void onDetachedFromWindow() {
   1059             super.onDetachedFromWindow();
   1060 
   1061             mBlink = false;
   1062             mBlinkState = true;
   1063 
   1064             mHandler.removeMessages(MESSAGE_BLINK);
   1065         }
   1066 
   1067         @Override
   1068         protected void dispatchDraw(Canvas canvas) {
   1069             if (mBlinkState) {
   1070                 super.dispatchDraw(canvas);
   1071             }
   1072         }
   1073     }
   1074 }
   1075