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