Fix for bug 6050753.
The method:
TypedArray: getLayoutDimension(int, String)
throws an undocumented exception when either the width and/or height
attributes are undefined.
See the bug report above for reasons why this was deemed unhelpful both
in the tools area and to developers in general.
Fix by:
1. Documenting the conditions under which the method raises an exception.
2. Deprecating the method, advising callers to supply a default instead.
Additionally, redefine the:
ViewGroup: setBaseAttributes(TypedArray, int, int);
methods to provide the appropriate defaults in ViewGroup subclasses as
advised above.
For the platform layouts the default value is WRAP_CONTENT (and is
defined in the ViewGroup.LayoutParams class). The special cases
are accomodated in LayoutParams subclasses in the following cases:
Subclass width height
FrameLayout.LayoutParams: MATCH_PARENT, MATCH_PARENT
TableLayout.LayoutParams: MATCH_PARENT, WRAP_CONTENT
TableRow.LayoutParams: MATCH_PARENT, WRAP_CONTENT
Change-Id: I335a3bd8e2d7f7866692898ed73492635a5b61ea
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 9628d6b..082cb56 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -20,6 +20,7 @@
import android.os.Handler;
import android.os.Message;
import android.widget.FrameLayout;
+import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -43,20 +44,20 @@
*
* <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
* (Context.LAYOUT_INFLATER_SERVICE);</pre>
- *
+ *
* <p>
* To create a new LayoutInflater with an additional {@link Factory} for your
* own views, you can use {@link #cloneInContext} to clone an existing
* ViewFactory, and then call {@link #setFactory} on it to include your
* Factory.
- *
+ *
* <p>
* For performance reasons, view inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
* to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource
* (R.<em>something</em> file.)
- *
+ *
* @see Context#getSystemService
*/
public abstract class LayoutInflater {
@@ -82,7 +83,7 @@
private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
new HashMap<String, Constructor<? extends View>>();
-
+
private HashMap<String, Boolean> mFilterMap;
private static final String TAG_MERGE = "merge";
@@ -93,36 +94,36 @@
/**
* Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
* to be inflated.
- *
+ *
*/
public interface Filter {
/**
* Hook to allow clients of the LayoutInflater to restrict the set of Views
* that are allowed to be inflated.
- *
+ *
* @param clazz The class object for the View that is about to be inflated
- *
+ *
* @return True if this class is allowed to be inflated, or false otherwise
*/
@SuppressWarnings("unchecked")
boolean onLoadClass(Class clazz);
}
-
+
public interface Factory {
/**
* Hook you can supply that is called when inflating from a LayoutInflater.
* You can use this to customize the tag names available in your XML
* layout files.
- *
+ *
* <p>
* Note that it is good practice to prefix these custom names with your
* package (i.e., com.coolcompany.apps) to avoid conflicts with system
* names.
- *
+ *
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
- *
+ *
* @return View Newly created view. Return null for the default
* behavior.
*/
@@ -150,14 +151,14 @@
private static class FactoryMerger implements Factory2 {
private final Factory mF1, mF2;
private final Factory2 mF12, mF22;
-
+
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}
-
+
public View onCreateView(String name, Context context, AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
@@ -172,13 +173,13 @@
: mF2.onCreateView(name, context, attrs);
}
}
-
+
/**
* Create a new LayoutInflater instance associated with a particular Context.
* Applications will almost always want to use
* {@link Context#getSystemService Context.getSystemService()} to retrieve
* the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
- *
+ *
* @param context The Context in which this LayoutInflater will create its
* Views; most importantly, this supplies the theme from which the default
* values for their attributes are retrieved.
@@ -191,7 +192,7 @@
* Create a new LayoutInflater instance that is a copy of an existing
* LayoutInflater, optionally with its Context changed. For use in
* implementing {@link #cloneInContext}.
- *
+ *
* @param original The original LayoutInflater to copy.
* @param newContext The new Context to use.
*/
@@ -202,7 +203,7 @@
mPrivateFactory = original.mPrivateFactory;
mFilter = original.mFilter;
}
-
+
/**
* Obtains the LayoutInflater from the given context.
*/
@@ -220,15 +221,15 @@
* pointing to a different Context than the original. This is used by
* {@link ContextThemeWrapper} to create a new LayoutInflater to go along
* with the new Context theme.
- *
+ *
* @param newContext The new Context to associate with the new LayoutInflater.
* May be the same as the original Context if desired.
- *
+ *
* @return Returns a brand spanking new LayoutInflater object associated with
* the given Context.
*/
public abstract LayoutInflater cloneInContext(Context newContext);
-
+
/**
* Return the context we are running in, for access to resources, class
* loader, etc.
@@ -264,7 +265,7 @@
* called on each element name as the xml is parsed. If the factory returns
* a View, that is added to the hierarchy. If it returns null, the next
* factory default {@link #onCreateView} method is called.
- *
+ *
* <p>If you have an existing
* LayoutInflater and want to add your own factory to it, use
* {@link #cloneInContext} to clone the existing instance and then you
@@ -320,13 +321,13 @@
public Filter getFilter() {
return mFilter;
}
-
+
/**
* Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
* which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
* throw an {@link InflateException}. This filter will replace any previous filter set on this
* LayoutInflater.
- *
+ *
* @param filter The Filter which restricts the set of Views that are allowed to be inflated.
* This filter will replace any previous filter set on this LayoutInflater.
*/
@@ -340,7 +341,7 @@
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
- *
+ *
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy.
@@ -360,7 +361,7 @@
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
- *
+ *
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy.
@@ -375,7 +376,7 @@
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
- *
+ *
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
@@ -407,7 +408,7 @@
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
- *
+ *
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy (if
@@ -442,7 +443,7 @@
}
final String name = parser.getName();
-
+
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
@@ -528,17 +529,17 @@
* Low-level function for instantiating a view by name. This attempts to
* instantiate a view class of the given <var>name</var> found in this
* LayoutInflater's ClassLoader.
- *
+ *
* <p>
* There are two things that can happen in an error case: either the
* exception describing the error will be thrown, or a null will be
* returned. You must deal with both possibilities -- the former will happen
* the first time createView() is called for a class of a particular name,
* the latter every time there-after for that class name.
- *
+ *
* @param name The full name of the class to be instantiated.
* @param attrs The XML attributes supplied for this instance.
- *
+ *
* @return View The newly instantiated view, or null.
*/
public final View createView(String name, String prefix, AttributeSet attrs)
@@ -551,7 +552,7 @@
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
-
+
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
@@ -569,7 +570,7 @@
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
-
+
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
@@ -625,10 +626,10 @@
* given the xml element name. Override it to handle custom view objects. If
* you override this in your subclass be sure to call through to
* super.onCreateView(name) for names you do not recognize.
- *
+ *
* @param name The fully qualified class name of the View to be create.
* @param attrs An AttributeSet of attributes to apply to the View.
- *
+ *
* @return View The View created.
*/
protected View onCreateView(String name, AttributeSet attrs)
@@ -672,7 +673,7 @@
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
}
-
+
if (view == null) {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
@@ -719,7 +720,7 @@
}
final String name = parser.getName();
-
+
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_INCLUDE.equals(name)) {
@@ -734,7 +735,7 @@
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
- viewGroup.addView(view, params);
+ viewGroup.addView(view, params);
} else {
final View view = createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
@@ -803,21 +804,14 @@
// We try to load the layout params set in the <include /> tag. If
// they don't exist, we will rely on the layout params set in the
// included XML file.
- // During a layoutparams generation, a runtime exception is thrown
- // if either layout_width or layout_height is missing. We catch
- // this exception and set localParams accordingly: true means we
- // successfully loaded layout params from the <include /> tag,
- // false means we need to rely on the included layout params.
- ViewGroup.LayoutParams params = null;
- try {
- params = group.generateLayoutParams(attrs);
- } catch (RuntimeException e) {
- params = group.generateLayoutParams(childAttrs);
- } finally {
- if (params != null) {
- view.setLayoutParams(params);
- }
- }
+ TypedArray ta = getContext().obtainStyledAttributes(attrs,
+ R.styleable.ViewGroup_Layout);
+ boolean definesBothWidthAndHeight =
+ ta.hasValue(R.styleable.ViewGroup_Layout_layout_width) &&
+ ta.hasValue(R.styleable.ViewGroup_Layout_layout_height);
+ AttributeSet attributes = definesBothWidthAndHeight ? attrs : childAttrs;
+ view.setLayoutParams(group.generateLayoutParams(attributes));
+ ta.recycle();
// Inflate all children.
rInflate(childParser, view, childAttrs, true);