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