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