• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view;
18 
19 import android.annotation.LayoutRes;
20 import android.annotation.Nullable;
21 import android.annotation.SystemService;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.content.res.XmlResourceParser;
26 import android.graphics.Canvas;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.os.Trace;
30 import android.util.AttributeSet;
31 import android.util.Log;
32 import android.util.TypedValue;
33 import android.util.Xml;
34 import android.widget.FrameLayout;
35 
36 import com.android.internal.R;
37 
38 import org.xmlpull.v1.XmlPullParser;
39 import org.xmlpull.v1.XmlPullParserException;
40 
41 import java.io.IOException;
42 import java.lang.reflect.Constructor;
43 import 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)
67 public 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")
onLoadClass(Class clazz)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          */
onCreateView(String name, Context context, AttributeSet attrs)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          */
onCreateView(View parent, String name, Context context, AttributeSet attrs)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 
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22)172         FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
173             mF1 = f1;
174             mF2 = f2;
175             mF12 = f12;
176             mF22 = f22;
177         }
178 
onCreateView(String name, Context context, AttributeSet attrs)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 
onCreateView(View parent, String name, Context context, AttributeSet attrs)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      */
LayoutInflater(Context context)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      */
LayoutInflater(LayoutInflater original, Context newContext)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      */
from(Context context)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      */
cloneInContext(Context newContext)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      */
getContext()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      */
getFactory()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      */
getFactory2()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      */
setFactory(Factory factory)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      */
setFactory2(Factory2 factory)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      */
setPrivateFactory(Factory2 factory)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      */
getFilter()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      */
setFilter(Filter filter)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      */
inflate(@ayoutRes int resource, @Nullable ViewGroup root)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      */
inflate(XmlPullParser parser, @Nullable ViewGroup root)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      */
inflate(@ayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)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      */
inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)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 
verifyClassLoader(Constructor<? extends View> constructor)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      */
createView(String name, String prefix, AttributeSet attrs)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      */
failNotAllowed(String name, String prefix, AttributeSet attrs)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      */
onCreateView(String name, AttributeSet attrs)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      */
onCreateView(View parent, String name, AttributeSet attrs)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      */
createViewFromTag(View parent, String name, Context context, AttributeSet attrs)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      */
createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr)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      */
rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate)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      */
rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate)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      */
parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)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 
parseInclude(XmlPullParser parser, Context context, View parent, AttributeSet attrs)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      */
consumeChildElements(XmlPullParser parser)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 
BlinkLayout(Context context, AttributeSet attrs)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 
makeBlink()1066         private void makeBlink() {
1067             Message message = mHandler.obtainMessage(MESSAGE_BLINK);
1068             mHandler.sendMessageDelayed(message, BLINK_DELAY);
1069         }
1070 
1071         @Override
onAttachedToWindow()1072         protected void onAttachedToWindow() {
1073             super.onAttachedToWindow();
1074 
1075             mBlink = true;
1076             mBlinkState = true;
1077 
1078             makeBlink();
1079         }
1080 
1081         @Override
onDetachedFromWindow()1082         protected void onDetachedFromWindow() {
1083             super.onDetachedFromWindow();
1084 
1085             mBlink = false;
1086             mBlinkState = true;
1087 
1088             mHandler.removeMessages(MESSAGE_BLINK);
1089         }
1090 
1091         @Override
dispatchDraw(Canvas canvas)1092         protected void dispatchDraw(Canvas canvas) {
1093             if (mBlinkState) {
1094                 super.dispatchDraw(canvas);
1095             }
1096         }
1097     }
1098 }
1099