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