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