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