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 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 >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><tag></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