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