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