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