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