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