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