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