• 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             if (root != null && root.getViewRootImpl() != null) {
644                 root.getViewRootImpl().notifyRendererOfExpensiveFrame();
645             }
646 
647             try {
648                 advanceToRootNode(parser);
649                 final String name = parser.getName();
650 
651                 if (DEBUG) {
652                     System.out.println("**************************");
653                     System.out.println("Creating root view: "
654                             + name);
655                     System.out.println("**************************");
656                 }
657 
658                 if (TAG_MERGE.equals(name)) {
659                     if (root == null || !attachToRoot) {
660                         throw new InflateException("<merge /> can be used only with a valid "
661                                 + "ViewGroup root and attachToRoot=true");
662                     }
663 
664                     rInflate(parser, root, inflaterContext, attrs, false);
665                 } else {
666                     // Temp is the root view that was found in the xml
667                     final View temp = createViewFromTag(root, name, inflaterContext, attrs);
668 
669                     if (root == null && temp != null && temp.getViewRootImpl() != null) {
670                         temp.getViewRootImpl().notifyRendererOfExpensiveFrame();
671                     }
672 
673                     ViewGroup.LayoutParams params = null;
674 
675                     if (root != null) {
676                         if (DEBUG) {
677                             System.out.println("Creating params from root: " +
678                                     root);
679                         }
680                         // Create layout params that match root, if supplied
681                         params = root.generateLayoutParams(attrs);
682                         if (!attachToRoot) {
683                             // Set the layout params for temp if we are not
684                             // attaching. (If we are, we use addView, below)
685                             temp.setLayoutParams(params);
686                         }
687                     }
688 
689                     if (DEBUG) {
690                         System.out.println("-----> start inflating children");
691                     }
692 
693                     // Inflate all children under temp against its context.
694                     rInflateChildren(parser, temp, attrs, true);
695 
696                     if (DEBUG) {
697                         System.out.println("-----> done inflating children");
698                     }
699 
700                     // We are supposed to attach all the views we found (int temp)
701                     // to root. Do that now.
702                     if (root != null && attachToRoot) {
703                         root.addView(temp, params);
704                     }
705 
706                     // Decide whether to return the root that was passed in or the
707                     // top view found in xml.
708                     if (root == null || !attachToRoot) {
709                         result = temp;
710                     }
711                 }
712 
713             } catch (XmlPullParserException e) {
714                 final InflateException ie = new InflateException(e.getMessage(), e);
715                 ie.setStackTrace(EMPTY_STACK_TRACE);
716                 throw ie;
717             } catch (Exception e) {
718                 final InflateException ie = new InflateException(
719                         getParserStateDescription(inflaterContext, attrs)
720                         + ": " + e.getMessage(), e);
721                 ie.setStackTrace(EMPTY_STACK_TRACE);
722                 throw ie;
723             } finally {
724                 // Don't retain static reference on context.
725                 mConstructorArgs[0] = lastContext;
726                 mConstructorArgs[1] = null;
727 
728                 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
729             }
730 
731             return result;
732         }
733     }
734 
getParserStateDescription(Context context, AttributeSet attrs)735     private static String getParserStateDescription(Context context, AttributeSet attrs) {
736         int sourceResId = Resources.getAttributeSetSourceResId(attrs);
737         if (sourceResId == Resources.ID_NULL) {
738             return attrs.getPositionDescription();
739         } else {
740             return attrs.getPositionDescription() + " in "
741                     + context.getResources().getResourceName(sourceResId);
742         }
743     }
744 
745     private static final ClassLoader BOOT_CLASS_LOADER = LayoutInflater.class.getClassLoader();
746 
verifyClassLoader(Constructor<? extends View> constructor)747     private final boolean verifyClassLoader(Constructor<? extends View> constructor) {
748         final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();
749         if (constructorLoader == BOOT_CLASS_LOADER) {
750             // fast path for boot class loader (most common case?) - always ok
751             return true;
752         }
753         // in all normal cases (no dynamic code loading), we will exit the following loop on the
754         // first iteration (i.e. when the declaring classloader is the contexts class loader).
755         ClassLoader cl = mContext.getClassLoader();
756         do {
757             if (constructorLoader == cl) {
758                 return true;
759             }
760             cl = cl.getParent();
761         } while (cl != null);
762         return false;
763     }
764     /**
765      * Low-level function for instantiating a view by name. This attempts to
766      * instantiate a view class of the given <var>name</var> found in this
767      * LayoutInflater's ClassLoader. To use an explicit Context in the View
768      * constructor, use {@link #createView(Context, String, String, AttributeSet)} instead.
769      *
770      * <p>
771      * There are two things that can happen in an error case: either the
772      * exception describing the error will be thrown, or a null will be
773      * returned. You must deal with both possibilities -- the former will happen
774      * the first time createView() is called for a class of a particular name,
775      * the latter every time there-after for that class name.
776      *
777      * @param name The full name of the class to be instantiated.
778      * @param attrs The XML attributes supplied for this instance.
779      *
780      * @return View The newly instantiated view, or null.
781      */
createView(String name, String prefix, AttributeSet attrs)782     public final View createView(String name, String prefix, AttributeSet attrs)
783             throws ClassNotFoundException, InflateException {
784         Context context = (Context) mConstructorArgs[0];
785         if (context == null) {
786             context = mContext;
787         }
788         return createView(context, name, prefix, attrs);
789     }
790 
791     /**
792      * Low-level function for instantiating a view by name. This attempts to
793      * instantiate a view class of the given <var>name</var> found in this
794      * LayoutInflater's ClassLoader.
795      *
796      * <p>
797      * There are two things that can happen in an error case: either the
798      * exception describing the error will be thrown, or a null will be
799      * returned. You must deal with both possibilities -- the former will happen
800      * the first time createView() is called for a class of a particular name,
801      * the latter every time there-after for that class name.
802      *
803      * @param viewContext The context used as the context parameter of the View constructor
804      * @param name The full name of the class to be instantiated.
805      * @param attrs The XML attributes supplied for this instance.
806      *
807      * @return View The newly instantiated view, or null.
808      */
809     @Nullable
createView(@onNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs)810     public final View createView(@NonNull Context viewContext, @NonNull String name,
811             @Nullable String prefix, @Nullable AttributeSet attrs)
812             throws ClassNotFoundException, InflateException {
813         Objects.requireNonNull(viewContext);
814         Objects.requireNonNull(name);
815         Constructor<? extends View> constructor = sConstructorMap.get(name);
816         if (constructor != null && !verifyClassLoader(constructor)) {
817             constructor = null;
818             sConstructorMap.remove(name);
819         }
820         Class<? extends View> clazz = null;
821 
822         try {
823             Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
824 
825             if (constructor == null) {
826                 // Class not found in the cache, see if it's real, and try to add it
827                 clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
828                         mContext.getClassLoader()).asSubclass(View.class);
829 
830                 if (mFilter != null && clazz != null) {
831                     boolean allowed = mFilter.onLoadClass(clazz);
832                     if (!allowed) {
833                         failNotAllowed(name, prefix, viewContext, attrs);
834                     }
835                 }
836                 constructor = clazz.getConstructor(mConstructorSignature);
837                 constructor.setAccessible(true);
838                 sConstructorMap.put(name, constructor);
839             } else {
840                 // If we have a filter, apply it to cached constructor
841                 if (mFilter != null) {
842                     // Have we seen this name before?
843                     Boolean allowedState = mFilterMap.get(name);
844                     if (allowedState == null) {
845                         // New class -- remember whether it is allowed
846                         clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
847                                 mContext.getClassLoader()).asSubclass(View.class);
848 
849                         boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
850                         mFilterMap.put(name, allowed);
851                         if (!allowed) {
852                             failNotAllowed(name, prefix, viewContext, attrs);
853                         }
854                     } else if (allowedState.equals(Boolean.FALSE)) {
855                         failNotAllowed(name, prefix, viewContext, attrs);
856                     }
857                 }
858             }
859 
860             Object lastContext = mConstructorArgs[0];
861             mConstructorArgs[0] = viewContext;
862             Object[] args = mConstructorArgs;
863             args[1] = attrs;
864 
865             try {
866                 final View view = constructor.newInstance(args);
867                 if (view instanceof ViewStub) {
868                     // Use the same context when inflating ViewStub later.
869                     final ViewStub viewStub = (ViewStub) view;
870                     viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
871                 }
872                 return view;
873             } finally {
874                 mConstructorArgs[0] = lastContext;
875             }
876         } catch (NoSuchMethodException e) {
877             final InflateException ie = new InflateException(
878                     getParserStateDescription(viewContext, attrs)
879                     + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
880             ie.setStackTrace(EMPTY_STACK_TRACE);
881             throw ie;
882 
883         } catch (ClassCastException e) {
884             // If loaded class is not a View subclass
885             final InflateException ie = new InflateException(
886                     getParserStateDescription(viewContext, attrs)
887                     + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
888             ie.setStackTrace(EMPTY_STACK_TRACE);
889             throw ie;
890         } catch (ClassNotFoundException e) {
891             // If loadClass fails, we should propagate the exception.
892             throw e;
893         } catch (Exception e) {
894             final InflateException ie = new InflateException(
895                     getParserStateDescription(viewContext, attrs) + ": Error inflating class "
896                             + (clazz == null ? "<unknown>" : clazz.getName()), e);
897             ie.setStackTrace(EMPTY_STACK_TRACE);
898             throw ie;
899         } finally {
900             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
901         }
902     }
903 
904     /**
905      * Throw an exception because the specified class is not allowed to be inflated.
906      */
failNotAllowed(String name, String prefix, Context context, AttributeSet attrs)907     private void failNotAllowed(String name, String prefix, Context context, AttributeSet attrs) {
908         throw new InflateException(getParserStateDescription(context, attrs)
909                 + ": Class not allowed to be inflated "+ (prefix != null ? (prefix + name) : name));
910     }
911 
912     /**
913      * This routine is responsible for creating the correct subclass of View
914      * given the xml element name. Override it to handle custom view objects. If
915      * you override this in your subclass be sure to call through to
916      * super.onCreateView(name) for names you do not recognize.
917      *
918      * @param name The fully qualified class name of the View to be create.
919      * @param attrs An AttributeSet of attributes to apply to the View.
920      *
921      * @return View The View created.
922      */
onCreateView(String name, AttributeSet attrs)923     protected View onCreateView(String name, AttributeSet attrs)
924             throws ClassNotFoundException {
925         return createView(name, "android.view.", attrs);
926     }
927 
928     /**
929      * Version of {@link #onCreateView(String, AttributeSet)} that also
930      * takes the future parent of the view being constructed.  The default
931      * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
932      *
933      * @param parent The future parent of the returned view.  <em>Note that
934      * this may be null.</em>
935      * @param name The fully qualified class name of the View to be create.
936      * @param attrs An AttributeSet of attributes to apply to the View.
937      *
938      * @return View The View created.
939      */
onCreateView(View parent, String name, AttributeSet attrs)940     protected View onCreateView(View parent, String name, AttributeSet attrs)
941             throws ClassNotFoundException {
942         return onCreateView(name, attrs);
943     }
944 
945     /**
946      * Version of {@link #onCreateView(View, String, AttributeSet)} that also
947      * takes the inflation context.  The default
948      * implementation simply calls {@link #onCreateView(View, String, AttributeSet)}.
949      *
950      * @param viewContext The Context to be used as a constructor parameter for the View
951      * @param parent The future parent of the returned view.  <em>Note that
952      * this may be null.</em>
953      * @param name The fully qualified class name of the View to be create.
954      * @param attrs An AttributeSet of attributes to apply to the View.
955      *
956      * @return View The View created.
957      */
958     @Nullable
onCreateView(@onNull Context viewContext, @Nullable View parent, @NonNull String name, @Nullable AttributeSet attrs)959     public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
960             @NonNull String name, @Nullable AttributeSet attrs)
961             throws ClassNotFoundException {
962         return onCreateView(parent, name, attrs);
963     }
964 
965     /**
966      * Convenience method for calling through to the five-arg createViewFromTag
967      * method. This method passes {@code false} for the {@code ignoreThemeAttr}
968      * argument and should be used for everything except {@code &gt;include>}
969      * tag parsing.
970      */
971     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
createViewFromTag(View parent, String name, Context context, AttributeSet attrs)972     private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
973         return createViewFromTag(parent, name, context, attrs, false);
974     }
975 
976     /**
977      * Creates a view from a tag name using the supplied attribute set.
978      * <p>
979      * <strong>Note:</strong> Default visibility so the BridgeInflater can
980      * override it.
981      *
982      * @param parent the parent view, used to inflate layout params
983      * @param name the name of the XML tag used to define the view
984      * @param context the inflation context for the view, typically the
985      *                {@code parent} or base layout inflater context
986      * @param attrs the attribute set for the XML tag used to define the view
987      * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
988      *                        attribute (if set) for the view being inflated,
989      *                        {@code false} otherwise
990      */
991     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr)992     View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
993             boolean ignoreThemeAttr) {
994         if (name.equals("view")) {
995             name = attrs.getAttributeValue(null, "class");
996         }
997 
998         // Apply a theme wrapper, if allowed and one is specified.
999         if (!ignoreThemeAttr) {
1000             final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
1001             final int themeResId = ta.getResourceId(0, 0);
1002             if (themeResId != 0) {
1003                 context = new ContextThemeWrapper(context, themeResId);
1004             }
1005             ta.recycle();
1006         }
1007 
1008         try {
1009             View view = tryCreateView(parent, name, context, attrs);
1010 
1011             if (view == null) {
1012                 final Object lastContext = mConstructorArgs[0];
1013                 mConstructorArgs[0] = context;
1014                 try {
1015                     if (-1 == name.indexOf('.')) {
1016                         view = onCreateView(context, parent, name, attrs);
1017                     } else {
1018                         view = createView(context, name, null, attrs);
1019                     }
1020                 } finally {
1021                     mConstructorArgs[0] = lastContext;
1022                 }
1023             }
1024 
1025             return view;
1026         } catch (InflateException e) {
1027             throw e;
1028 
1029         } catch (ClassNotFoundException e) {
1030             final InflateException ie = new InflateException(
1031                     getParserStateDescription(context, attrs)
1032                     + ": Error inflating class " + name, e);
1033             ie.setStackTrace(EMPTY_STACK_TRACE);
1034             throw ie;
1035 
1036         } catch (Exception e) {
1037             final InflateException ie = new InflateException(
1038                     getParserStateDescription(context, attrs)
1039                     + ": Error inflating class " + name, e);
1040             ie.setStackTrace(EMPTY_STACK_TRACE);
1041             throw ie;
1042         }
1043     }
1044 
1045     /**
1046      * Tries to create a view from a tag name using the supplied attribute set.
1047      *
1048      * This method gives the factory provided by {@link LayoutInflater#setFactory} and
1049      * {@link LayoutInflater#setFactory2} a chance to create a view. However, it does not apply all
1050      * of the general view creation logic, and thus may return {@code null} for some tags. This
1051      * method is used by {@link LayoutInflater#inflate} in creating {@code View} objects.
1052      *
1053      * @hide for use by precompiled layouts.
1054      *
1055      * @param parent the parent view, used to inflate layout params
1056      * @param name the name of the XML tag used to define the view
1057      * @param context the inflation context for the view, typically the
1058      *                {@code parent} or base layout inflater context
1059      * @param attrs the attribute set for the XML tag used to define the view
1060      */
1061     @UnsupportedAppUsage(trackingBug = 122360734)
1062     @Nullable
tryCreateView(@ullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs)1063     public final View tryCreateView(@Nullable View parent, @NonNull String name,
1064         @NonNull Context context,
1065         @NonNull AttributeSet attrs) {
1066         if (name.equals(TAG_1995)) {
1067             // Let's party like it's 1995!
1068             return new BlinkLayout(context, attrs);
1069         }
1070 
1071         View view;
1072         if (mFactory2 != null) {
1073             view = mFactory2.onCreateView(parent, name, context, attrs);
1074         } else if (mFactory != null) {
1075             view = mFactory.onCreateView(name, context, attrs);
1076         } else {
1077             view = null;
1078         }
1079 
1080         if (view == null && mPrivateFactory != null) {
1081             view = mPrivateFactory.onCreateView(parent, name, context, attrs);
1082         }
1083 
1084         return view;
1085     }
1086 
1087     /**
1088      * Recursive method used to inflate internal (non-root) children. This
1089      * method calls through to {@link #rInflate} using the parent context as
1090      * the inflation context.
1091      * <strong>Note:</strong> Default visibility so the BridgeInflater can
1092      * call it.
1093      */
rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate)1094     final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
1095             boolean finishInflate) throws XmlPullParserException, IOException {
1096         rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
1097     }
1098 
1099     /**
1100      * Recursive method used to descend down the xml hierarchy and instantiate
1101      * views, instantiate their children, and then call onFinishInflate().
1102      * <p>
1103      * <strong>Note:</strong> Default visibility so the BridgeInflater can
1104      * override it.
1105      */
rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate)1106     void rInflate(XmlPullParser parser, View parent, Context context,
1107             AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
1108 
1109         final int depth = parser.getDepth();
1110         int type;
1111         boolean pendingRequestFocus = false;
1112 
1113         while (((type = parser.next()) != XmlPullParser.END_TAG ||
1114                 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1115 
1116             if (type != XmlPullParser.START_TAG) {
1117                 continue;
1118             }
1119 
1120             final String name = parser.getName();
1121 
1122             if (TAG_REQUEST_FOCUS.equals(name)) {
1123                 pendingRequestFocus = true;
1124                 consumeChildElements(parser);
1125             } else if (TAG_TAG.equals(name)) {
1126                 parseViewTag(parser, parent, attrs);
1127             } else if (TAG_INCLUDE.equals(name)) {
1128                 if (parser.getDepth() == 0) {
1129                     throw new InflateException("<include /> cannot be the root element");
1130                 }
1131                 parseInclude(parser, context, parent, attrs);
1132             } else if (TAG_MERGE.equals(name)) {
1133                 throw new InflateException("<merge /> must be the root element");
1134             } else {
1135                 final View view = createViewFromTag(parent, name, context, attrs);
1136                 final ViewGroup viewGroup = (ViewGroup) parent;
1137                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
1138                 rInflateChildren(parser, view, attrs, true);
1139                 viewGroup.addView(view, params);
1140             }
1141         }
1142 
1143         if (pendingRequestFocus) {
1144             parent.restoreDefaultFocus();
1145         }
1146 
1147         if (finishInflate) {
1148             parent.onFinishInflate();
1149         }
1150     }
1151 
1152     /**
1153      * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
1154      * containing View.
1155      */
parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)1156     private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
1157             throws XmlPullParserException, IOException {
1158         final Context context = view.getContext();
1159         final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
1160         final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
1161         final CharSequence value = ta.getText(R.styleable.ViewTag_value);
1162         view.setTag(key, value);
1163         ta.recycle();
1164 
1165         consumeChildElements(parser);
1166     }
1167 
1168     @UnsupportedAppUsage
parseInclude(XmlPullParser parser, Context context, View parent, AttributeSet attrs)1169     private void parseInclude(XmlPullParser parser, Context context, View parent,
1170             AttributeSet attrs) throws XmlPullParserException, IOException {
1171         int type;
1172 
1173         if (!(parent instanceof ViewGroup)) {
1174             throw new InflateException("<include /> can only be used inside of a ViewGroup");
1175         }
1176 
1177         // Apply a theme wrapper, if requested. This is sort of a weird
1178         // edge case, since developers think the <include> overwrites
1179         // values in the AttributeSet of the included View. So, if the
1180         // included View has a theme attribute, we'll need to ignore it.
1181         final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
1182         final int themeResId = ta.getResourceId(0, 0);
1183         final boolean hasThemeOverride = themeResId != 0;
1184         if (hasThemeOverride) {
1185             context = new ContextThemeWrapper(context, themeResId);
1186         }
1187         ta.recycle();
1188 
1189         // If the layout is pointing to a theme attribute, we have to
1190         // massage the value to get a resource identifier out of it.
1191         int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
1192         if (layout == 0) {
1193             final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
1194             if (value == null || value.length() <= 0) {
1195                 throw new InflateException("You must specify a layout in the"
1196                     + " include tag: <include layout=\"@layout/layoutID\" />");
1197             }
1198 
1199             // Attempt to resolve the "?attr/name" string to an attribute
1200             // within the default (e.g. application) package.
1201             layout = context.getResources().getIdentifier(
1202                 value.substring(1), "attr", context.getPackageName());
1203 
1204         }
1205 
1206         // The layout might be referencing a theme attribute.
1207         if (mTempValue == null) {
1208             mTempValue = new TypedValue();
1209         }
1210         if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
1211             layout = mTempValue.resourceId;
1212         }
1213 
1214         if (layout == 0) {
1215             final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
1216             throw new InflateException("You must specify a valid layout "
1217                 + "reference. The layout ID " + value + " is not valid.");
1218         }
1219 
1220         final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
1221             (ViewGroup) parent, /*attachToRoot=*/true);
1222         if (precompiled == null) {
1223             final XmlResourceParser childParser = context.getResources().getLayout(layout);
1224 
1225             try {
1226                 final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
1227 
1228                 while ((type = childParser.next()) != XmlPullParser.START_TAG &&
1229                     type != XmlPullParser.END_DOCUMENT) {
1230                     // Empty.
1231                 }
1232 
1233                 if (type != XmlPullParser.START_TAG) {
1234                     throw new InflateException(getParserStateDescription(context, childAttrs)
1235                             + ": No start tag found!");
1236                 }
1237 
1238                 final String childName = childParser.getName();
1239 
1240                 if (TAG_MERGE.equals(childName)) {
1241                     // The <merge> tag doesn't support android:theme, so
1242                     // nothing special to do here.
1243                     rInflate(childParser, parent, context, childAttrs, false);
1244                 } else {
1245                     final View view = createViewFromTag(parent, childName,
1246                         context, childAttrs, hasThemeOverride);
1247                     final ViewGroup group = (ViewGroup) parent;
1248 
1249                     final TypedArray a = context.obtainStyledAttributes(
1250                         attrs, R.styleable.Include);
1251                     final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
1252                     final int visibility = a.getInt(R.styleable.Include_visibility, -1);
1253                     a.recycle();
1254 
1255                     // We try to load the layout params set in the <include /> tag.
1256                     // If the parent can't generate layout params (ex. missing width
1257                     // or height for the framework ViewGroups, though this is not
1258                     // necessarily true of all ViewGroups) then we expect it to throw
1259                     // a runtime exception.
1260                     // We catch this exception and set localParams accordingly: true
1261                     // means we successfully loaded layout params from the <include>
1262                     // tag, false means we need to rely on the included layout params.
1263                     ViewGroup.LayoutParams params = null;
1264                     try {
1265                         params = group.generateLayoutParams(attrs);
1266                     } catch (RuntimeException e) {
1267                         // Ignore, just fail over to child attrs.
1268                     }
1269                     if (params == null) {
1270                         params = group.generateLayoutParams(childAttrs);
1271                     }
1272                     view.setLayoutParams(params);
1273 
1274                     // Inflate all children.
1275                     rInflateChildren(childParser, view, childAttrs, true);
1276 
1277                     if (id != View.NO_ID) {
1278                         view.setId(id);
1279                     }
1280 
1281                     switch (visibility) {
1282                         case 0:
1283                             view.setVisibility(View.VISIBLE);
1284                             break;
1285                         case 1:
1286                             view.setVisibility(View.INVISIBLE);
1287                             break;
1288                         case 2:
1289                             view.setVisibility(View.GONE);
1290                             break;
1291                     }
1292 
1293                     group.addView(view);
1294                 }
1295             } finally {
1296                 childParser.close();
1297             }
1298         }
1299         LayoutInflater.consumeChildElements(parser);
1300     }
1301 
1302     /**
1303      * <strong>Note:</strong> default visibility so that
1304      * LayoutInflater_Delegate can call it.
1305      */
consumeChildElements(XmlPullParser parser)1306     final static void consumeChildElements(XmlPullParser parser)
1307             throws XmlPullParserException, IOException {
1308         int type;
1309         final int currentDepth = parser.getDepth();
1310         while (((type = parser.next()) != XmlPullParser.END_TAG ||
1311                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
1312             // Empty
1313         }
1314     }
1315 
1316     private static class BlinkLayout extends FrameLayout {
1317         private static final int MESSAGE_BLINK = 0x42;
1318         private static final int BLINK_DELAY = 500;
1319 
1320         private boolean mBlink;
1321         private boolean mBlinkState;
1322         private final Handler mHandler;
1323 
BlinkLayout(Context context, AttributeSet attrs)1324         public BlinkLayout(Context context, AttributeSet attrs) {
1325             super(context, attrs);
1326             mHandler = new Handler(new Handler.Callback() {
1327                 @Override
1328                 public boolean handleMessage(Message msg) {
1329                     if (msg.what == MESSAGE_BLINK) {
1330                         if (mBlink) {
1331                             mBlinkState = !mBlinkState;
1332                             makeBlink();
1333                         }
1334                         invalidate();
1335                         return true;
1336                     }
1337                     return false;
1338                 }
1339             });
1340         }
1341 
makeBlink()1342         private void makeBlink() {
1343             Message message = mHandler.obtainMessage(MESSAGE_BLINK);
1344             mHandler.sendMessageDelayed(message, BLINK_DELAY);
1345         }
1346 
1347         @Override
onAttachedToWindow()1348         protected void onAttachedToWindow() {
1349             super.onAttachedToWindow();
1350 
1351             mBlink = true;
1352             mBlinkState = true;
1353 
1354             makeBlink();
1355         }
1356 
1357         @Override
onDetachedFromWindow()1358         protected void onDetachedFromWindow() {
1359             super.onDetachedFromWindow();
1360 
1361             mBlink = false;
1362             mBlinkState = true;
1363 
1364             mHandler.removeMessages(MESSAGE_BLINK);
1365         }
1366 
1367         @Override
dispatchDraw(Canvas canvas)1368         protected void dispatchDraw(Canvas canvas) {
1369             if (mBlinkState) {
1370                 super.dispatchDraw(canvas);
1371             }
1372         }
1373     }
1374 }
1375