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