• 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.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemService;
23 import android.annotation.TestApi;
24 import android.compat.annotation.UnsupportedAppUsage;
25 import android.content.Context;
26 import android.content.pm.ApplicationInfo;
27 import android.content.res.Resources;
28 import android.content.res.TypedArray;
29 import android.content.res.XmlResourceParser;
30 import android.graphics.Canvas;
31 import android.os.Build;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.os.Trace;
35 import android.util.AttributeSet;
36 import android.util.Log;
37 import android.util.TypedValue;
38 import android.util.Xml;
39 import android.widget.FrameLayout;
40 
41 import com.android.internal.R;
42 
43 import dalvik.system.PathClassLoader;
44 
45 import org.xmlpull.v1.XmlPullParser;
46 import org.xmlpull.v1.XmlPullParserException;
47 
48 import java.io.File;
49 import java.io.IOException;
50 import java.lang.reflect.Constructor;
51 import java.lang.reflect.Method;
52 import java.util.HashMap;
53 import java.util.Objects;
54 
55 /**
56  * Instantiates a layout XML file into its corresponding {@link android.view.View}
57  * objects. It is never used directly. Instead, use
58  * {@link android.app.Activity#getLayoutInflater()} or
59  * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
60  * that is already hooked up to the current context and correctly configured
61  * for the device you are running on.
62  * <p>
63  * To create a new LayoutInflater with an additional {@link Factory} for your
64  * own views, you can use {@link #cloneInContext} to clone an existing
65  * ViewFactory, and then call {@link #setFactory} on it to include your
66  * Factory.
67  * <p>
68  * For performance reasons, view inflation relies heavily on pre-processing of
69  * XML files that is done at build time. Therefore, it is not currently possible
70  * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
71  * it only works with an XmlPullParser returned from a compiled resource
72  * (R.<em>something</em> file.)
73  * <p>
74  * <strong>Note:</strong> This class is <strong>not</strong> thread-safe and a given
75  * instance should only be accessed by a single thread.
76  */
77 @SystemService(Context.LAYOUT_INFLATER_SERVICE)
78 public abstract class LayoutInflater {
79 
80     private static final String TAG = LayoutInflater.class.getSimpleName();
81     private static final boolean DEBUG = false;
82 
83     private static final String COMPILED_VIEW_DEX_FILE_NAME = "/compiled_view.dex";
84     /**
85      * Whether or not we use the precompiled layout.
86      */
87     private static final String USE_PRECOMPILED_LAYOUT = "view.precompiled_layout_enabled";
88 
89     /** Empty stack trace used to avoid log spam in re-throw exceptions. */
90     private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
91 
92     /**
93      * This field should be made private, so it is hidden from the SDK.
94      * {@hide}
95      */
96     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
97     protected final Context mContext;
98 
99     // these are optional, set by the caller
100     /**
101      * If any developer has desire to change this value, they should instead use
102      * {@link #cloneInContext(Context)} and set the new factory in thew newly-created
103      * LayoutInflater.
104      */
105     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
106     private boolean mFactorySet;
107     @UnsupportedAppUsage
108     private Factory mFactory;
109     @UnsupportedAppUsage
110     private Factory2 mFactory2;
111     @UnsupportedAppUsage
112     private Factory2 mPrivateFactory;
113     private Filter mFilter;
114 
115     // Indicates whether we should try to inflate layouts using a precompiled layout instead of
116     // inflating from the XML resource.
117     private boolean mUseCompiledView;
118     // This variable holds the classloader that will be used to look for precompiled layouts. The
119     // The classloader includes the generated compiled_view.dex file.
120     private ClassLoader mPrecompiledClassLoader;
121 
122     /**
123      * This is not a public API. Two APIs are now available to alleviate the need to access
124      * this directly: {@link #createView(Context, String, String, AttributeSet)} and
125      * {@link #onCreateView(Context, View, String, AttributeSet)}.
126      */
127     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
128     final Object[] mConstructorArgs = new Object[2];
129 
130     @UnsupportedAppUsage
131     static final Class<?>[] mConstructorSignature = new Class[] {
132             Context.class, AttributeSet.class};
133 
134     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769490)
135     private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
136             new HashMap<String, Constructor<? extends View>>();
137 
138     private HashMap<String, Boolean> mFilterMap;
139 
140     private TypedValue mTempValue;
141 
142     private static final String TAG_MERGE = "merge";
143     private static final String TAG_INCLUDE = "include";
144     private static final String TAG_1995 = "blink";
145     private static final String TAG_REQUEST_FOCUS = "requestFocus";
146     private static final String TAG_TAG = "tag";
147 
148     private static final String ATTR_LAYOUT = "layout";
149 
150     @UnsupportedAppUsage
151     private static final int[] ATTRS_THEME = new int[] {
152             com.android.internal.R.attr.theme };
153 
154     /**
155      * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
156      * to be inflated.
157      *
158      */
159     public interface Filter {
160         /**
161          * Hook to allow clients of the LayoutInflater to restrict the set of Views
162          * that are allowed to be inflated.
163          *
164          * @param clazz The class object for the View that is about to be inflated
165          *
166          * @return True if this class is allowed to be inflated, or false otherwise
167          */
168         @SuppressWarnings("unchecked")
onLoadClass(Class clazz)169         boolean onLoadClass(Class clazz);
170     }
171 
172     public interface Factory {
173         /**
174          * Hook you can supply that is called when inflating from a LayoutInflater.
175          * You can use this to customize the tag names available in your XML
176          * layout files.
177          *
178          * <p>
179          * Note that it is good practice to prefix these custom names with your
180          * package (i.e., com.coolcompany.apps) to avoid conflicts with system
181          * names.
182          *
183          * @param name Tag name to be inflated.
184          * @param context The context the view is being created in.
185          * @param attrs Inflation attributes as specified in XML file.
186          *
187          * @return View Newly created view. Return null for the default
188          *         behavior.
189          */
190         @Nullable
onCreateView(@onNull String name, @NonNull Context context, @NonNull AttributeSet attrs)191         View onCreateView(@NonNull String name, @NonNull Context context,
192                 @NonNull AttributeSet attrs);
193     }
194 
195     public interface Factory2 extends Factory {
196         /**
197          * Version of {@link #onCreateView(String, Context, AttributeSet)}
198          * that also supplies the parent that the view created view will be
199          * placed in.
200          *
201          * @param parent The parent that the created view will be placed
202          * in; <em>note that this may be null</em>.
203          * @param name Tag name to be inflated.
204          * @param context The context the view is being created in.
205          * @param attrs Inflation attributes as specified in XML file.
206          *
207          * @return View Newly created view. Return null for the default
208          *         behavior.
209          */
210         @Nullable
onCreateView(@ullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs)211         View onCreateView(@Nullable View parent, @NonNull String name,
212                 @NonNull Context context, @NonNull AttributeSet attrs);
213     }
214 
215     private static class FactoryMerger implements Factory2 {
216         private final Factory mF1, mF2;
217         private final Factory2 mF12, mF22;
218 
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22)219         FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
220             mF1 = f1;
221             mF2 = f2;
222             mF12 = f12;
223             mF22 = f22;
224         }
225 
226         @Nullable
onCreateView(@onNull String name, @NonNull Context context, @NonNull AttributeSet attrs)227         public View onCreateView(@NonNull String name, @NonNull Context context,
228                 @NonNull AttributeSet attrs) {
229             View v = mF1.onCreateView(name, context, attrs);
230             if (v != null) return v;
231             return mF2.onCreateView(name, context, attrs);
232         }
233 
234         @Nullable
onCreateView(@ullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs)235         public View onCreateView(@Nullable View parent, @NonNull String name,
236                 @NonNull Context context, @NonNull AttributeSet attrs) {
237             View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
238                     : mF1.onCreateView(name, context, attrs);
239             if (v != null) return v;
240             return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
241                     : mF2.onCreateView(name, context, attrs);
242         }
243     }
244 
245     /**
246      * Create a new LayoutInflater instance associated with a particular Context.
247      * Applications will almost always want to use
248      * {@link Context#getSystemService Context.getSystemService()} to retrieve
249      * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
250      *
251      * @param context The Context in which this LayoutInflater will create its
252      * Views; most importantly, this supplies the theme from which the default
253      * values for their attributes are retrieved.
254      */
LayoutInflater(Context context)255     protected LayoutInflater(Context context) {
256         mContext = context;
257         initPrecompiledViews();
258     }
259 
260     /**
261      * Create a new LayoutInflater instance that is a copy of an existing
262      * LayoutInflater, optionally with its Context changed.  For use in
263      * implementing {@link #cloneInContext}.
264      *
265      * @param original The original LayoutInflater to copy.
266      * @param newContext The new Context to use.
267      */
LayoutInflater(LayoutInflater original, Context newContext)268     protected LayoutInflater(LayoutInflater original, Context newContext) {
269         mContext = newContext;
270         mFactory = original.mFactory;
271         mFactory2 = original.mFactory2;
272         mPrivateFactory = original.mPrivateFactory;
273         setFilter(original.mFilter);
274         initPrecompiledViews();
275     }
276 
277     /**
278      * Obtains the LayoutInflater from the given context.
279      */
from(Context context)280     public static LayoutInflater from(Context context) {
281         LayoutInflater LayoutInflater =
282                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
283         if (LayoutInflater == null) {
284             throw new AssertionError("LayoutInflater not found.");
285         }
286         return LayoutInflater;
287     }
288 
289     /**
290      * Create a copy of the existing LayoutInflater object, with the copy
291      * pointing to a different Context than the original.  This is used by
292      * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
293      * with the new Context theme.
294      *
295      * @param newContext The new Context to associate with the new LayoutInflater.
296      * May be the same as the original Context if desired.
297      *
298      * @return Returns a brand spanking new LayoutInflater object associated with
299      * the given Context.
300      */
cloneInContext(Context newContext)301     public abstract LayoutInflater cloneInContext(Context newContext);
302 
303     /**
304      * Return the context we are running in, for access to resources, class
305      * loader, etc.
306      */
getContext()307     public Context getContext() {
308         return mContext;
309     }
310 
311     /**
312      * Return the current {@link Factory} (or null). This is called on each element
313      * name. If the factory returns a View, add that to the hierarchy. If it
314      * returns null, proceed to call onCreateView(name).
315      */
getFactory()316     public final Factory getFactory() {
317         return mFactory;
318     }
319 
320     /**
321      * Return the current {@link Factory2}.  Returns null if no factory is set
322      * or the set factory does not implement the {@link Factory2} interface.
323      * This is called on each element
324      * name. If the factory returns a View, add that to the hierarchy. If it
325      * returns null, proceed to call onCreateView(name).
326      */
getFactory2()327     public final Factory2 getFactory2() {
328         return mFactory2;
329     }
330 
331     /**
332      * Attach a custom Factory interface for creating views while using
333      * this LayoutInflater.  This must not be null, and can only be set once;
334      * after setting, you can not change the factory.  This is
335      * called on each element name as the xml is parsed. If the factory returns
336      * a View, that is added to the hierarchy. If it returns null, the next
337      * factory default {@link #onCreateView} method is called.
338      *
339      * <p>If you have an existing
340      * LayoutInflater and want to add your own factory to it, use
341      * {@link #cloneInContext} to clone the existing instance and then you
342      * can use this function (once) on the returned new instance.  This will
343      * merge your own factory with whatever factory the original instance is
344      * using.
345      */
setFactory(Factory factory)346     public void setFactory(Factory factory) {
347         if (mFactorySet) {
348             throw new IllegalStateException("A factory has already been set on this LayoutInflater");
349         }
350         if (factory == null) {
351             throw new NullPointerException("Given factory can not be null");
352         }
353         mFactorySet = true;
354         if (mFactory == null) {
355             mFactory = factory;
356         } else {
357             mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
358         }
359     }
360 
361     /**
362      * Like {@link #setFactory}, but allows you to set a {@link Factory2}
363      * interface.
364      */
setFactory2(Factory2 factory)365     public void setFactory2(Factory2 factory) {
366         if (mFactorySet) {
367             throw new IllegalStateException("A factory has already been set on this LayoutInflater");
368         }
369         if (factory == null) {
370             throw new NullPointerException("Given factory can not be null");
371         }
372         mFactorySet = true;
373         if (mFactory == null) {
374             mFactory = mFactory2 = factory;
375         } else {
376             mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
377         }
378     }
379 
380     /**
381      * @hide for use by framework
382      */
383     @UnsupportedAppUsage
setPrivateFactory(Factory2 factory)384     public void setPrivateFactory(Factory2 factory) {
385         if (mPrivateFactory == null) {
386             mPrivateFactory = factory;
387         } else {
388             mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
389         }
390     }
391 
392     /**
393      * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
394      * that are allowed to be inflated.
395      */
getFilter()396     public Filter getFilter() {
397         return mFilter;
398     }
399 
400     /**
401      * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
402      * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
403      * throw an {@link InflateException}. This filter will replace any previous filter set on this
404      * LayoutInflater.
405      *
406      * @param filter The Filter which restricts the set of Views that are allowed to be inflated.
407      *        This filter will replace any previous filter set on this LayoutInflater.
408      */
setFilter(Filter filter)409     public void setFilter(Filter filter) {
410         mFilter = filter;
411         if (filter != null) {
412             mFilterMap = new HashMap<String, Boolean>();
413         }
414     }
415 
initPrecompiledViews()416     private void initPrecompiledViews() {
417         // Precompiled layouts are not supported in this release.
418         boolean enabled = false;
419         initPrecompiledViews(enabled);
420     }
421 
initPrecompiledViews(boolean enablePrecompiledViews)422     private void initPrecompiledViews(boolean enablePrecompiledViews) {
423         mUseCompiledView = enablePrecompiledViews;
424 
425         if (!mUseCompiledView) {
426             mPrecompiledClassLoader = null;
427             return;
428         }
429 
430         // Make sure the application allows code generation
431         ApplicationInfo appInfo = mContext.getApplicationInfo();
432         if (appInfo.isEmbeddedDexUsed() || appInfo.isPrivilegedApp()) {
433             mUseCompiledView = false;
434             return;
435         }
436 
437         // Try to load the precompiled layout file.
438         try {
439             mPrecompiledClassLoader = mContext.getClassLoader();
440             String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME;
441             if (new File(dexFile).exists()) {
442                 mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader);
443             } else {
444                 // If the precompiled layout file doesn't exist, then disable precompiled
445                 // layouts.
446                 mUseCompiledView = false;
447             }
448         } catch (Throwable e) {
449             if (DEBUG) {
450                 Log.e(TAG, "Failed to initialized precompiled views layouts", e);
451             }
452             mUseCompiledView = false;
453         }
454         if (!mUseCompiledView) {
455             mPrecompiledClassLoader = null;
456         }
457     }
458 
459     /**
460      * @hide for use by CTS tests
461      */
462     @TestApi
setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts)463     public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) {
464         initPrecompiledViews(enablePrecompiledLayouts);
465     }
466 
467     /**
468      * Inflate a new view hierarchy from the specified xml resource. Throws
469      * {@link InflateException} if there is an error.
470      *
471      * @param resource ID for an XML layout resource to load (e.g.,
472      *        <code>R.layout.main_page</code>)
473      * @param root Optional view to be the parent of the generated hierarchy.
474      * @return The root View of the inflated hierarchy. If root was supplied,
475      *         this is the root View; otherwise it is the root of the inflated
476      *         XML file.
477      */
inflate(@ayoutRes int resource, @Nullable ViewGroup root)478     public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
479         return inflate(resource, root, root != null);
480     }
481 
482     /**
483      * Inflate a new view hierarchy from the specified xml node. Throws
484      * {@link InflateException} if there is an error. *
485      * <p>
486      * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
487      * reasons, view inflation relies heavily on pre-processing of XML files
488      * that is done at build time. Therefore, it is not currently possible to
489      * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
490      *
491      * @param parser XML dom node containing the description of the view
492      *        hierarchy.
493      * @param root Optional view to be the parent of the generated hierarchy.
494      * @return The root View of the inflated hierarchy. If root was supplied,
495      *         this is the root View; otherwise it is the root of the inflated
496      *         XML file.
497      */
inflate(XmlPullParser parser, @Nullable ViewGroup root)498     public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
499         return inflate(parser, root, root != null);
500     }
501 
502     /**
503      * Inflate a new view hierarchy from the specified xml resource. Throws
504      * {@link InflateException} if there is an error.
505      *
506      * @param resource ID for an XML layout resource to load (e.g.,
507      *        <code>R.layout.main_page</code>)
508      * @param root Optional view to be the parent of the generated hierarchy (if
509      *        <em>attachToRoot</em> is true), or else simply an object that
510      *        provides a set of LayoutParams values for root of the returned
511      *        hierarchy (if <em>attachToRoot</em> is false.)
512      * @param attachToRoot Whether the inflated hierarchy should be attached to
513      *        the root parameter? If false, root is only used to create the
514      *        correct subclass of LayoutParams for the root view in the XML.
515      * @return The root View of the inflated hierarchy. If root was supplied and
516      *         attachToRoot is true, this is root; otherwise it is the root of
517      *         the inflated XML file.
518      */
inflate(@ayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)519     public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
520         final Resources res = getContext().getResources();
521         if (DEBUG) {
522             Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
523                   + Integer.toHexString(resource) + ")");
524         }
525 
526         View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
527         if (view != null) {
528             return view;
529         }
530         XmlResourceParser parser = res.getLayout(resource);
531         try {
532             return inflate(parser, root, attachToRoot);
533         } finally {
534             parser.close();
535         }
536     }
537 
538     private @Nullable
tryInflatePrecompiled(@ayoutRes int resource, Resources res, @Nullable ViewGroup root, boolean attachToRoot)539     View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
540         boolean attachToRoot) {
541         if (!mUseCompiledView) {
542             return null;
543         }
544 
545         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
546 
547         // Try to inflate using a precompiled layout.
548         String pkg = res.getResourcePackageName(resource);
549         String layout = res.getResourceEntryName(resource);
550 
551         try {
552             Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
553             Method inflater = clazz.getMethod(layout, Context.class, int.class);
554             View view = (View) inflater.invoke(null, mContext, resource);
555 
556             if (view != null && root != null) {
557                 // We were able to use the precompiled inflater, but now we need to do some work to
558                 // attach the view to the root correctly.
559                 XmlResourceParser parser = res.getLayout(resource);
560                 try {
561                     AttributeSet attrs = Xml.asAttributeSet(parser);
562                     advanceToRootNode(parser);
563                     ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
564 
565                     if (attachToRoot) {
566                         root.addView(view, params);
567                     } else {
568                         view.setLayoutParams(params);
569                     }
570                 } finally {
571                     parser.close();
572                 }
573             }
574 
575             return view;
576         } catch (Throwable e) {
577             if (DEBUG) {
578                 Log.e(TAG, "Failed to use precompiled view", e);
579             }
580         } finally {
581             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
582         }
583         return null;
584     }
585 
586     /**
587      * Advances the given parser to the first START_TAG. Throws InflateException if no start tag is
588      * found.
589      */
advanceToRootNode(XmlPullParser parser)590     private void advanceToRootNode(XmlPullParser parser)
591         throws InflateException, IOException, XmlPullParserException {
592         // Look for the root node.
593         int type;
594         while ((type = parser.next()) != XmlPullParser.START_TAG &&
595             type != XmlPullParser.END_DOCUMENT) {
596             // Empty
597         }
598 
599         if (type != XmlPullParser.START_TAG) {
600             throw new InflateException(parser.getPositionDescription()
601                 + ": No start tag found!");
602         }
603     }
604 
605     /**
606      * Inflate a new view hierarchy from the specified XML node. Throws
607      * {@link InflateException} if there is an error.
608      * <p>
609      * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
610      * reasons, view inflation relies heavily on pre-processing of XML files
611      * that is done at build time. Therefore, it is not currently possible to
612      * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
613      *
614      * @param parser XML dom node containing the description of the view
615      *        hierarchy.
616      * @param root Optional view to be the parent of the generated hierarchy (if
617      *        <em>attachToRoot</em> is true), or else simply an object that
618      *        provides a set of LayoutParams values for root of the returned
619      *        hierarchy (if <em>attachToRoot</em> is false.)
620      * @param attachToRoot Whether the inflated hierarchy should be attached to
621      *        the root parameter? If false, root is only used to create the
622      *        correct subclass of LayoutParams for the root view in the XML.
623      * @return The root View of the inflated hierarchy. If root was supplied and
624      *         attachToRoot is true, this is root; otherwise it is the root of
625      *         the inflated XML file.
626      */
inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)627     public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
628         synchronized (mConstructorArgs) {
629             Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
630 
631             final Context inflaterContext = mContext;
632             final AttributeSet attrs = Xml.asAttributeSet(parser);
633             Context lastContext = (Context) mConstructorArgs[0];
634             mConstructorArgs[0] = inflaterContext;
635             View result = root;
636 
637             try {
638                 advanceToRootNode(parser);
639                 final String name = parser.getName();
640 
641                 if (DEBUG) {
642                     System.out.println("**************************");
643                     System.out.println("Creating root view: "
644                             + name);
645                     System.out.println("**************************");
646                 }
647 
648                 if (TAG_MERGE.equals(name)) {
649                     if (root == null || !attachToRoot) {
650                         throw new InflateException("<merge /> can be used only with a valid "
651                                 + "ViewGroup root and attachToRoot=true");
652                     }
653 
654                     rInflate(parser, root, inflaterContext, attrs, false);
655                 } else {
656                     // Temp is the root view that was found in the xml
657                     final View temp = createViewFromTag(root, name, inflaterContext, attrs);
658 
659                     ViewGroup.LayoutParams params = null;
660 
661                     if (root != null) {
662                         if (DEBUG) {
663                             System.out.println("Creating params from root: " +
664                                     root);
665                         }
666                         // Create layout params that match root, if supplied
667                         params = root.generateLayoutParams(attrs);
668                         if (!attachToRoot) {
669                             // Set the layout params for temp if we are not
670                             // attaching. (If we are, we use addView, below)
671                             temp.setLayoutParams(params);
672                         }
673                     }
674 
675                     if (DEBUG) {
676                         System.out.println("-----> start inflating children");
677                     }
678 
679                     // Inflate all children under temp against its context.
680                     rInflateChildren(parser, temp, attrs, true);
681 
682                     if (DEBUG) {
683                         System.out.println("-----> done inflating children");
684                     }
685 
686                     // We are supposed to attach all the views we found (int temp)
687                     // to root. Do that now.
688                     if (root != null && attachToRoot) {
689                         root.addView(temp, params);
690                     }
691 
692                     // Decide whether to return the root that was passed in or the
693                     // top view found in xml.
694                     if (root == null || !attachToRoot) {
695                         result = temp;
696                     }
697                 }
698 
699             } catch (XmlPullParserException e) {
700                 final InflateException ie = new InflateException(e.getMessage(), e);
701                 ie.setStackTrace(EMPTY_STACK_TRACE);
702                 throw ie;
703             } catch (Exception e) {
704                 final InflateException ie = new InflateException(
705                         getParserStateDescription(inflaterContext, attrs)
706                         + ": " + e.getMessage(), e);
707                 ie.setStackTrace(EMPTY_STACK_TRACE);
708                 throw ie;
709             } finally {
710                 // Don't retain static reference on context.
711                 mConstructorArgs[0] = lastContext;
712                 mConstructorArgs[1] = null;
713 
714                 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
715             }
716 
717             return result;
718         }
719     }
720 
getParserStateDescription(Context context, AttributeSet attrs)721     private static String getParserStateDescription(Context context, AttributeSet attrs) {
722         int sourceResId = Resources.getAttributeSetSourceResId(attrs);
723         if (sourceResId == Resources.ID_NULL) {
724             return attrs.getPositionDescription();
725         } else {
726             return attrs.getPositionDescription() + " in "
727                     + context.getResources().getResourceName(sourceResId);
728         }
729     }
730 
731     private static final ClassLoader BOOT_CLASS_LOADER = LayoutInflater.class.getClassLoader();
732 
verifyClassLoader(Constructor<? extends View> constructor)733     private final boolean verifyClassLoader(Constructor<? extends View> constructor) {
734         final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();
735         if (constructorLoader == BOOT_CLASS_LOADER) {
736             // fast path for boot class loader (most common case?) - always ok
737             return true;
738         }
739         // in all normal cases (no dynamic code loading), we will exit the following loop on the
740         // first iteration (i.e. when the declaring classloader is the contexts class loader).
741         ClassLoader cl = mContext.getClassLoader();
742         do {
743             if (constructorLoader == cl) {
744                 return true;
745             }
746             cl = cl.getParent();
747         } while (cl != null);
748         return false;
749     }
750     /**
751      * Low-level function for instantiating a view by name. This attempts to
752      * instantiate a view class of the given <var>name</var> found in this
753      * LayoutInflater's ClassLoader. To use an explicit Context in the View
754      * constructor, use {@link #createView(Context, String, String, AttributeSet)} instead.
755      *
756      * <p>
757      * There are two things that can happen in an error case: either the
758      * exception describing the error will be thrown, or a null will be
759      * returned. You must deal with both possibilities -- the former will happen
760      * the first time createView() is called for a class of a particular name,
761      * the latter every time there-after for that class name.
762      *
763      * @param name The full name of the class to be instantiated.
764      * @param attrs The XML attributes supplied for this instance.
765      *
766      * @return View The newly instantiated view, or null.
767      */
createView(String name, String prefix, AttributeSet attrs)768     public final View createView(String name, String prefix, AttributeSet attrs)
769             throws ClassNotFoundException, InflateException {
770         Context context = (Context) mConstructorArgs[0];
771         if (context == null) {
772             context = mContext;
773         }
774         return createView(context, name, prefix, attrs);
775     }
776 
777     /**
778      * Low-level function for instantiating a view by name. This attempts to
779      * instantiate a view class of the given <var>name</var> found in this
780      * LayoutInflater's ClassLoader.
781      *
782      * <p>
783      * There are two things that can happen in an error case: either the
784      * exception describing the error will be thrown, or a null will be
785      * returned. You must deal with both possibilities -- the former will happen
786      * the first time createView() is called for a class of a particular name,
787      * the latter every time there-after for that class name.
788      *
789      * @param viewContext The context used as the context parameter of the View constructor
790      * @param name The full name of the class to be instantiated.
791      * @param attrs The XML attributes supplied for this instance.
792      *
793      * @return View The newly instantiated view, or null.
794      */
795     @Nullable
createView(@onNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs)796     public final View createView(@NonNull Context viewContext, @NonNull String name,
797             @Nullable String prefix, @Nullable AttributeSet attrs)
798             throws ClassNotFoundException, InflateException {
799         Objects.requireNonNull(viewContext);
800         Objects.requireNonNull(name);
801         Constructor<? extends View> constructor = sConstructorMap.get(name);
802         if (constructor != null && !verifyClassLoader(constructor)) {
803             constructor = null;
804             sConstructorMap.remove(name);
805         }
806         Class<? extends View> clazz = null;
807 
808         try {
809             Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
810 
811             if (constructor == null) {
812                 // Class not found in the cache, see if it's real, and try to add it
813                 clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
814                         mContext.getClassLoader()).asSubclass(View.class);
815 
816                 if (mFilter != null && clazz != null) {
817                     boolean allowed = mFilter.onLoadClass(clazz);
818                     if (!allowed) {
819                         failNotAllowed(name, prefix, viewContext, attrs);
820                     }
821                 }
822                 constructor = clazz.getConstructor(mConstructorSignature);
823                 constructor.setAccessible(true);
824                 sConstructorMap.put(name, constructor);
825             } else {
826                 // If we have a filter, apply it to cached constructor
827                 if (mFilter != null) {
828                     // Have we seen this name before?
829                     Boolean allowedState = mFilterMap.get(name);
830                     if (allowedState == null) {
831                         // New class -- remember whether it is allowed
832                         clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
833                                 mContext.getClassLoader()).asSubclass(View.class);
834 
835                         boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
836                         mFilterMap.put(name, allowed);
837                         if (!allowed) {
838                             failNotAllowed(name, prefix, viewContext, attrs);
839                         }
840                     } else if (allowedState.equals(Boolean.FALSE)) {
841                         failNotAllowed(name, prefix, viewContext, attrs);
842                     }
843                 }
844             }
845 
846             Object lastContext = mConstructorArgs[0];
847             mConstructorArgs[0] = viewContext;
848             Object[] args = mConstructorArgs;
849             args[1] = attrs;
850 
851             try {
852                 final View view = constructor.newInstance(args);
853                 if (view instanceof ViewStub) {
854                     // Use the same context when inflating ViewStub later.
855                     final ViewStub viewStub = (ViewStub) view;
856                     viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
857                 }
858                 return view;
859             } finally {
860                 mConstructorArgs[0] = lastContext;
861             }
862         } catch (NoSuchMethodException e) {
863             final InflateException ie = new InflateException(
864                     getParserStateDescription(viewContext, attrs)
865                     + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
866             ie.setStackTrace(EMPTY_STACK_TRACE);
867             throw ie;
868 
869         } catch (ClassCastException e) {
870             // If loaded class is not a View subclass
871             final InflateException ie = new InflateException(
872                     getParserStateDescription(viewContext, attrs)
873                     + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
874             ie.setStackTrace(EMPTY_STACK_TRACE);
875             throw ie;
876         } catch (ClassNotFoundException e) {
877             // If loadClass fails, we should propagate the exception.
878             throw e;
879         } catch (Exception e) {
880             final InflateException ie = new InflateException(
881                     getParserStateDescription(viewContext, attrs) + ": Error inflating class "
882                             + (clazz == null ? "<unknown>" : clazz.getName()), e);
883             ie.setStackTrace(EMPTY_STACK_TRACE);
884             throw ie;
885         } finally {
886             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
887         }
888     }
889 
890     /**
891      * Throw an exception because the specified class is not allowed to be inflated.
892      */
failNotAllowed(String name, String prefix, Context context, AttributeSet attrs)893     private void failNotAllowed(String name, String prefix, Context context, AttributeSet attrs) {
894         throw new InflateException(getParserStateDescription(context, attrs)
895                 + ": Class not allowed to be inflated "+ (prefix != null ? (prefix + name) : name));
896     }
897 
898     /**
899      * This routine is responsible for creating the correct subclass of View
900      * given the xml element name. Override it to handle custom view objects. If
901      * you override this in your subclass be sure to call through to
902      * super.onCreateView(name) for names you do not recognize.
903      *
904      * @param name The fully qualified class name of the View to be create.
905      * @param attrs An AttributeSet of attributes to apply to the View.
906      *
907      * @return View The View created.
908      */
onCreateView(String name, AttributeSet attrs)909     protected View onCreateView(String name, AttributeSet attrs)
910             throws ClassNotFoundException {
911         return createView(name, "android.view.", attrs);
912     }
913 
914     /**
915      * Version of {@link #onCreateView(String, AttributeSet)} that also
916      * takes the future parent of the view being constructed.  The default
917      * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
918      *
919      * @param parent The future parent of the returned view.  <em>Note that
920      * this may be null.</em>
921      * @param name The fully qualified class name of the View to be create.
922      * @param attrs An AttributeSet of attributes to apply to the View.
923      *
924      * @return View The View created.
925      */
onCreateView(View parent, String name, AttributeSet attrs)926     protected View onCreateView(View parent, String name, AttributeSet attrs)
927             throws ClassNotFoundException {
928         return onCreateView(name, attrs);
929     }
930 
931     /**
932      * Version of {@link #onCreateView(View, String, AttributeSet)} that also
933      * takes the inflation context.  The default
934      * implementation simply calls {@link #onCreateView(View, String, AttributeSet)}.
935      *
936      * @param viewContext The Context to be used as a constructor parameter for the View
937      * @param parent The future parent of the returned view.  <em>Note that
938      * this may be null.</em>
939      * @param name The fully qualified class name of the View to be create.
940      * @param attrs An AttributeSet of attributes to apply to the View.
941      *
942      * @return View The View created.
943      */
944     @Nullable
onCreateView(@onNull Context viewContext, @Nullable View parent, @NonNull String name, @Nullable AttributeSet attrs)945     public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
946             @NonNull String name, @Nullable AttributeSet attrs)
947             throws ClassNotFoundException {
948         return onCreateView(parent, name, attrs);
949     }
950 
951     /**
952      * Convenience method for calling through to the five-arg createViewFromTag
953      * method. This method passes {@code false} for the {@code ignoreThemeAttr}
954      * argument and should be used for everything except {@code &gt;include>}
955      * tag parsing.
956      */
957     @UnsupportedAppUsage
createViewFromTag(View parent, String name, Context context, AttributeSet attrs)958     private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
959         return createViewFromTag(parent, name, context, attrs, false);
960     }
961 
962     /**
963      * Creates a view from a tag name using the supplied attribute set.
964      * <p>
965      * <strong>Note:</strong> Default visibility so the BridgeInflater can
966      * override it.
967      *
968      * @param parent the parent view, used to inflate layout params
969      * @param name the name of the XML tag used to define the view
970      * @param context the inflation context for the view, typically the
971      *                {@code parent} or base layout inflater context
972      * @param attrs the attribute set for the XML tag used to define the view
973      * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
974      *                        attribute (if set) for the view being inflated,
975      *                        {@code false} otherwise
976      */
977     @UnsupportedAppUsage
createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr)978     View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
979             boolean ignoreThemeAttr) {
980         if (name.equals("view")) {
981             name = attrs.getAttributeValue(null, "class");
982         }
983 
984         // Apply a theme wrapper, if allowed and one is specified.
985         if (!ignoreThemeAttr) {
986             final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
987             final int themeResId = ta.getResourceId(0, 0);
988             if (themeResId != 0) {
989                 context = new ContextThemeWrapper(context, themeResId);
990             }
991             ta.recycle();
992         }
993 
994         try {
995             View view = tryCreateView(parent, name, context, attrs);
996 
997             if (view == null) {
998                 final Object lastContext = mConstructorArgs[0];
999                 mConstructorArgs[0] = context;
1000                 try {
1001                     if (-1 == name.indexOf('.')) {
1002                         view = onCreateView(context, parent, name, attrs);
1003                     } else {
1004                         view = createView(context, name, null, attrs);
1005                     }
1006                 } finally {
1007                     mConstructorArgs[0] = lastContext;
1008                 }
1009             }
1010 
1011             return view;
1012         } catch (InflateException e) {
1013             throw e;
1014 
1015         } catch (ClassNotFoundException e) {
1016             final InflateException ie = new InflateException(
1017                     getParserStateDescription(context, attrs)
1018                     + ": Error inflating class " + name, e);
1019             ie.setStackTrace(EMPTY_STACK_TRACE);
1020             throw ie;
1021 
1022         } catch (Exception e) {
1023             final InflateException ie = new InflateException(
1024                     getParserStateDescription(context, attrs)
1025                     + ": Error inflating class " + name, e);
1026             ie.setStackTrace(EMPTY_STACK_TRACE);
1027             throw ie;
1028         }
1029     }
1030 
1031     /**
1032      * Tries to create a view from a tag name using the supplied attribute set.
1033      *
1034      * This method gives the factory provided by {@link LayoutInflater#setFactory} and
1035      * {@link LayoutInflater#setFactory2} a chance to create a view. However, it does not apply all
1036      * of the general view creation logic, and thus may return {@code null} for some tags. This
1037      * method is used by {@link LayoutInflater#inflate} in creating {@code View} objects.
1038      *
1039      * @hide for use by precompiled layouts.
1040      *
1041      * @param parent the parent view, used to inflate layout params
1042      * @param name the name of the XML tag used to define the view
1043      * @param context the inflation context for the view, typically the
1044      *                {@code parent} or base layout inflater context
1045      * @param attrs the attribute set for the XML tag used to define the view
1046      */
1047     @UnsupportedAppUsage(trackingBug = 122360734)
1048     @Nullable
tryCreateView(@ullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs)1049     public final View tryCreateView(@Nullable View parent, @NonNull String name,
1050         @NonNull Context context,
1051         @NonNull AttributeSet attrs) {
1052         if (name.equals(TAG_1995)) {
1053             // Let's party like it's 1995!
1054             return new BlinkLayout(context, attrs);
1055         }
1056 
1057         View view;
1058         if (mFactory2 != null) {
1059             view = mFactory2.onCreateView(parent, name, context, attrs);
1060         } else if (mFactory != null) {
1061             view = mFactory.onCreateView(name, context, attrs);
1062         } else {
1063             view = null;
1064         }
1065 
1066         if (view == null && mPrivateFactory != null) {
1067             view = mPrivateFactory.onCreateView(parent, name, context, attrs);
1068         }
1069 
1070         return view;
1071     }
1072 
1073     /**
1074      * Recursive method used to inflate internal (non-root) children. This
1075      * method calls through to {@link #rInflate} using the parent context as
1076      * the inflation context.
1077      * <strong>Note:</strong> Default visibility so the BridgeInflater can
1078      * call it.
1079      */
rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate)1080     final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
1081             boolean finishInflate) throws XmlPullParserException, IOException {
1082         rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
1083     }
1084 
1085     /**
1086      * Recursive method used to descend down the xml hierarchy and instantiate
1087      * views, instantiate their children, and then call onFinishInflate().
1088      * <p>
1089      * <strong>Note:</strong> Default visibility so the BridgeInflater can
1090      * override it.
1091      */
rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate)1092     void rInflate(XmlPullParser parser, View parent, Context context,
1093             AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
1094 
1095         final int depth = parser.getDepth();
1096         int type;
1097         boolean pendingRequestFocus = false;
1098 
1099         while (((type = parser.next()) != XmlPullParser.END_TAG ||
1100                 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1101 
1102             if (type != XmlPullParser.START_TAG) {
1103                 continue;
1104             }
1105 
1106             final String name = parser.getName();
1107 
1108             if (TAG_REQUEST_FOCUS.equals(name)) {
1109                 pendingRequestFocus = true;
1110                 consumeChildElements(parser);
1111             } else if (TAG_TAG.equals(name)) {
1112                 parseViewTag(parser, parent, attrs);
1113             } else if (TAG_INCLUDE.equals(name)) {
1114                 if (parser.getDepth() == 0) {
1115                     throw new InflateException("<include /> cannot be the root element");
1116                 }
1117                 parseInclude(parser, context, parent, attrs);
1118             } else if (TAG_MERGE.equals(name)) {
1119                 throw new InflateException("<merge /> must be the root element");
1120             } else {
1121                 final View view = createViewFromTag(parent, name, context, attrs);
1122                 final ViewGroup viewGroup = (ViewGroup) parent;
1123                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
1124                 rInflateChildren(parser, view, attrs, true);
1125                 viewGroup.addView(view, params);
1126             }
1127         }
1128 
1129         if (pendingRequestFocus) {
1130             parent.restoreDefaultFocus();
1131         }
1132 
1133         if (finishInflate) {
1134             parent.onFinishInflate();
1135         }
1136     }
1137 
1138     /**
1139      * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
1140      * containing View.
1141      */
parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)1142     private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
1143             throws XmlPullParserException, IOException {
1144         final Context context = view.getContext();
1145         final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
1146         final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
1147         final CharSequence value = ta.getText(R.styleable.ViewTag_value);
1148         view.setTag(key, value);
1149         ta.recycle();
1150 
1151         consumeChildElements(parser);
1152     }
1153 
1154     @UnsupportedAppUsage
parseInclude(XmlPullParser parser, Context context, View parent, AttributeSet attrs)1155     private void parseInclude(XmlPullParser parser, Context context, View parent,
1156             AttributeSet attrs) throws XmlPullParserException, IOException {
1157         int type;
1158 
1159         if (!(parent instanceof ViewGroup)) {
1160             throw new InflateException("<include /> can only be used inside of a ViewGroup");
1161         }
1162 
1163         // Apply a theme wrapper, if requested. This is sort of a weird
1164         // edge case, since developers think the <include> overwrites
1165         // values in the AttributeSet of the included View. So, if the
1166         // included View has a theme attribute, we'll need to ignore it.
1167         final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
1168         final int themeResId = ta.getResourceId(0, 0);
1169         final boolean hasThemeOverride = themeResId != 0;
1170         if (hasThemeOverride) {
1171             context = new ContextThemeWrapper(context, themeResId);
1172         }
1173         ta.recycle();
1174 
1175         // If the layout is pointing to a theme attribute, we have to
1176         // massage the value to get a resource identifier out of it.
1177         int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
1178         if (layout == 0) {
1179             final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
1180             if (value == null || value.length() <= 0) {
1181                 throw new InflateException("You must specify a layout in the"
1182                     + " include tag: <include layout=\"@layout/layoutID\" />");
1183             }
1184 
1185             // Attempt to resolve the "?attr/name" string to an attribute
1186             // within the default (e.g. application) package.
1187             layout = context.getResources().getIdentifier(
1188                 value.substring(1), "attr", context.getPackageName());
1189 
1190         }
1191 
1192         // The layout might be referencing a theme attribute.
1193         if (mTempValue == null) {
1194             mTempValue = new TypedValue();
1195         }
1196         if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
1197             layout = mTempValue.resourceId;
1198         }
1199 
1200         if (layout == 0) {
1201             final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
1202             throw new InflateException("You must specify a valid layout "
1203                 + "reference. The layout ID " + value + " is not valid.");
1204         }
1205 
1206         final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
1207             (ViewGroup) parent, /*attachToRoot=*/true);
1208         if (precompiled == null) {
1209             final XmlResourceParser childParser = context.getResources().getLayout(layout);
1210 
1211             try {
1212                 final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
1213 
1214                 while ((type = childParser.next()) != XmlPullParser.START_TAG &&
1215                     type != XmlPullParser.END_DOCUMENT) {
1216                     // Empty.
1217                 }
1218 
1219                 if (type != XmlPullParser.START_TAG) {
1220                     throw new InflateException(getParserStateDescription(context, childAttrs)
1221                             + ": No start tag found!");
1222                 }
1223 
1224                 final String childName = childParser.getName();
1225 
1226                 if (TAG_MERGE.equals(childName)) {
1227                     // The <merge> tag doesn't support android:theme, so
1228                     // nothing special to do here.
1229                     rInflate(childParser, parent, context, childAttrs, false);
1230                 } else {
1231                     final View view = createViewFromTag(parent, childName,
1232                         context, childAttrs, hasThemeOverride);
1233                     final ViewGroup group = (ViewGroup) parent;
1234 
1235                     final TypedArray a = context.obtainStyledAttributes(
1236                         attrs, R.styleable.Include);
1237                     final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
1238                     final int visibility = a.getInt(R.styleable.Include_visibility, -1);
1239                     a.recycle();
1240 
1241                     // We try to load the layout params set in the <include /> tag.
1242                     // If the parent can't generate layout params (ex. missing width
1243                     // or height for the framework ViewGroups, though this is not
1244                     // necessarily true of all ViewGroups) then we expect it to throw
1245                     // a runtime exception.
1246                     // We catch this exception and set localParams accordingly: true
1247                     // means we successfully loaded layout params from the <include>
1248                     // tag, false means we need to rely on the included layout params.
1249                     ViewGroup.LayoutParams params = null;
1250                     try {
1251                         params = group.generateLayoutParams(attrs);
1252                     } catch (RuntimeException e) {
1253                         // Ignore, just fail over to child attrs.
1254                     }
1255                     if (params == null) {
1256                         params = group.generateLayoutParams(childAttrs);
1257                     }
1258                     view.setLayoutParams(params);
1259 
1260                     // Inflate all children.
1261                     rInflateChildren(childParser, view, childAttrs, true);
1262 
1263                     if (id != View.NO_ID) {
1264                         view.setId(id);
1265                     }
1266 
1267                     switch (visibility) {
1268                         case 0:
1269                             view.setVisibility(View.VISIBLE);
1270                             break;
1271                         case 1:
1272                             view.setVisibility(View.INVISIBLE);
1273                             break;
1274                         case 2:
1275                             view.setVisibility(View.GONE);
1276                             break;
1277                     }
1278 
1279                     group.addView(view);
1280                 }
1281             } finally {
1282                 childParser.close();
1283             }
1284         }
1285         LayoutInflater.consumeChildElements(parser);
1286     }
1287 
1288     /**
1289      * <strong>Note:</strong> default visibility so that
1290      * LayoutInflater_Delegate can call it.
1291      */
consumeChildElements(XmlPullParser parser)1292     final static void consumeChildElements(XmlPullParser parser)
1293             throws XmlPullParserException, IOException {
1294         int type;
1295         final int currentDepth = parser.getDepth();
1296         while (((type = parser.next()) != XmlPullParser.END_TAG ||
1297                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
1298             // Empty
1299         }
1300     }
1301 
1302     private static class BlinkLayout extends FrameLayout {
1303         private static final int MESSAGE_BLINK = 0x42;
1304         private static final int BLINK_DELAY = 500;
1305 
1306         private boolean mBlink;
1307         private boolean mBlinkState;
1308         private final Handler mHandler;
1309 
BlinkLayout(Context context, AttributeSet attrs)1310         public BlinkLayout(Context context, AttributeSet attrs) {
1311             super(context, attrs);
1312             mHandler = new Handler(new Handler.Callback() {
1313                 @Override
1314                 public boolean handleMessage(Message msg) {
1315                     if (msg.what == MESSAGE_BLINK) {
1316                         if (mBlink) {
1317                             mBlinkState = !mBlinkState;
1318                             makeBlink();
1319                         }
1320                         invalidate();
1321                         return true;
1322                     }
1323                     return false;
1324                 }
1325             });
1326         }
1327 
makeBlink()1328         private void makeBlink() {
1329             Message message = mHandler.obtainMessage(MESSAGE_BLINK);
1330             mHandler.sendMessageDelayed(message, BLINK_DELAY);
1331         }
1332 
1333         @Override
onAttachedToWindow()1334         protected void onAttachedToWindow() {
1335             super.onAttachedToWindow();
1336 
1337             mBlink = true;
1338             mBlinkState = true;
1339 
1340             makeBlink();
1341         }
1342 
1343         @Override
onDetachedFromWindow()1344         protected void onDetachedFromWindow() {
1345             super.onDetachedFromWindow();
1346 
1347             mBlink = false;
1348             mBlinkState = true;
1349 
1350             mHandler.removeMessages(MESSAGE_BLINK);
1351         }
1352 
1353         @Override
dispatchDraw(Canvas canvas)1354         protected void dispatchDraw(Canvas canvas) {
1355             if (mBlinkState) {
1356                 super.dispatchDraw(canvas);
1357             }
1358         }
1359     }
1360 }
1361