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