LayoutInflater.java revision 6194d728cf9d43a3a40f8a0e96283d92887c5bcd
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 com.android.internal.R;
20
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.annotation.LayoutRes;
25import android.annotation.Nullable;
26import android.content.Context;
27import android.content.res.Resources;
28import android.content.res.TypedArray;
29import android.content.res.XmlResourceParser;
30import android.graphics.Canvas;
31import android.os.Handler;
32import android.os.Message;
33import android.os.Trace;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.util.TypedValue;
37import android.util.Xml;
38import android.widget.FrameLayout;
39
40import java.io.IOException;
41import java.lang.reflect.Constructor;
42import 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 */
70public 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                sConstructorMap.put(name, constructor);
594            } else {
595                // If we have a filter, apply it to cached constructor
596                if (mFilter != null) {
597                    // Have we seen this name before?
598                    Boolean allowedState = mFilterMap.get(name);
599                    if (allowedState == null) {
600                        // New class -- remember whether it is allowed
601                        clazz = mContext.getClassLoader().loadClass(
602                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
603
604                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
605                        mFilterMap.put(name, allowed);
606                        if (!allowed) {
607                            failNotAllowed(name, prefix, attrs);
608                        }
609                    } else if (allowedState.equals(Boolean.FALSE)) {
610                        failNotAllowed(name, prefix, attrs);
611                    }
612                }
613            }
614
615            Object[] args = mConstructorArgs;
616            args[1] = attrs;
617
618            constructor.setAccessible(true);
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                        final boolean hasWidth = a.hasValue(R.styleable.Include_layout_width);
950                        final boolean hasHeight = a.hasValue(R.styleable.Include_layout_height);
951                        a.recycle();
952
953                        // We try to load the layout params set in the <include /> tag. If
954                        // they don't exist, we will rely on the layout params set in the
955                        // included XML file.
956                        // During a layoutparams generation, a runtime exception is thrown
957                        // if either layout_width or layout_height is missing. We catch
958                        // this exception and set localParams accordingly: true means we
959                        // successfully loaded layout params from the <include /> tag,
960                        // false means we need to rely on the included layout params.
961                        ViewGroup.LayoutParams params = null;
962                        if (hasWidth && hasHeight) {
963                            try {
964                                params = group.generateLayoutParams(attrs);
965                            } catch (RuntimeException e) {
966                                // Ignore, just fail over to child attrs.
967                            }
968                        }
969                        if (params == null) {
970                            params = group.generateLayoutParams(childAttrs);
971                        }
972                        view.setLayoutParams(params);
973
974                        // Inflate all children.
975                        rInflateChildren(childParser, view, childAttrs, true);
976
977                        if (id != View.NO_ID) {
978                            view.setId(id);
979                        }
980
981                        switch (visibility) {
982                            case 0:
983                                view.setVisibility(View.VISIBLE);
984                                break;
985                            case 1:
986                                view.setVisibility(View.INVISIBLE);
987                                break;
988                            case 2:
989                                view.setVisibility(View.GONE);
990                                break;
991                        }
992
993                        group.addView(view);
994                    }
995                } finally {
996                    childParser.close();
997                }
998            }
999        } else {
1000            throw new InflateException("<include /> can only be used inside of a ViewGroup");
1001        }
1002
1003        LayoutInflater.consumeChildElements(parser);
1004    }
1005
1006    /**
1007     * <strong>Note:</strong> default visibility so that
1008     * LayoutInflater_Delegate can call it.
1009     */
1010    final static void consumeChildElements(XmlPullParser parser)
1011            throws XmlPullParserException, IOException {
1012        int type;
1013        final int currentDepth = parser.getDepth();
1014        while (((type = parser.next()) != XmlPullParser.END_TAG ||
1015                parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
1016            // Empty
1017        }
1018    }
1019
1020    private static class BlinkLayout extends FrameLayout {
1021        private static final int MESSAGE_BLINK = 0x42;
1022        private static final int BLINK_DELAY = 500;
1023
1024        private boolean mBlink;
1025        private boolean mBlinkState;
1026        private final Handler mHandler;
1027
1028        public BlinkLayout(Context context, AttributeSet attrs) {
1029            super(context, attrs);
1030            mHandler = new Handler(new Handler.Callback() {
1031                @Override
1032                public boolean handleMessage(Message msg) {
1033                    if (msg.what == MESSAGE_BLINK) {
1034                        if (mBlink) {
1035                            mBlinkState = !mBlinkState;
1036                            makeBlink();
1037                        }
1038                        invalidate();
1039                        return true;
1040                    }
1041                    return false;
1042                }
1043            });
1044        }
1045
1046        private void makeBlink() {
1047            Message message = mHandler.obtainMessage(MESSAGE_BLINK);
1048            mHandler.sendMessageDelayed(message, BLINK_DELAY);
1049        }
1050
1051        @Override
1052        protected void onAttachedToWindow() {
1053            super.onAttachedToWindow();
1054
1055            mBlink = true;
1056            mBlinkState = true;
1057
1058            makeBlink();
1059        }
1060
1061        @Override
1062        protected void onDetachedFromWindow() {
1063            super.onDetachedFromWindow();
1064
1065            mBlink = false;
1066            mBlinkState = true;
1067
1068            mHandler.removeMessages(MESSAGE_BLINK);
1069        }
1070
1071        @Override
1072        protected void dispatchDraw(Canvas canvas) {
1073            if (mBlinkState) {
1074                super.dispatchDraw(canvas);
1075            }
1076        }
1077    }
1078}
1079