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