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