blob: 66a94fa48fddf894c89e850a3b99e070a79d9aea [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Xml;
import java.io.IOException;
/**
* Delegate used to provide new implementation of a select few methods of {@link LayoutInflater}
*
* Through the layoutlib_create tool, the original methods of LayoutInflater have been replaced
* by calls to methods of the same name in this delegate class.
*
*/
public class LayoutInflater_Delegate {
private static final String TAG_MERGE = "merge";
private static final String ATTR_LAYOUT = "layout";
private static final int[] ATTRS_THEME = new int[] {
com.android.internal.R.attr.theme };
public static boolean sIsInInclude = false;
/**
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
*
* This implementation just records the merge status before calling the default implementation.
*/
@LayoutlibDelegate
/* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser,
View parent, Context context, AttributeSet attrs, boolean finishInflate)
throws XmlPullParserException, IOException {
if (finishInflate == false) {
// this is a merge rInflate!
if (thisInflater instanceof BridgeInflater) {
((BridgeInflater) thisInflater).setIsInMerge(true);
}
}
// ---- START DEFAULT IMPLEMENTATION.
thisInflater.rInflate_Original(parser, parent, context, attrs, finishInflate);
// ---- END DEFAULT IMPLEMENTATION.
if (finishInflate == false) {
// this is a merge rInflate!
if (thisInflater instanceof BridgeInflater) {
((BridgeInflater) thisInflater).setIsInMerge(false);
}
}
}
@LayoutlibDelegate
public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser,
Context context, View parent, AttributeSet attrs)
throws XmlPullParserException, IOException {
int type;
if (parent instanceof ViewGroup) {
// Apply a theme wrapper, if requested. This is sort of a weird
// edge case, since developers think the <include> overwrites
// values in the AttributeSet of the included View. So, if the
// included View has a theme attribute, we'll need to ignore it.
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
final boolean hasThemeOverride = themeResId != 0;
if (hasThemeOverride) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
// If the layout is pointing to a theme attribute, we have to
// massage the value to get a resource identifier out of it.
int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null || value.length() <= 0) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />", null, null);
LayoutInflater.consumeChildElements(parser);
return;
}
// Attempt to resolve the "?attr/name" string to an identifier.
layout = context.getResources().getIdentifier(value.substring(1), null, null);
}
// The layout might be referencing a theme attribute.
// ---- START CHANGES
if (layout != 0) {
final TypedValue tempValue = new TypedValue();
if (context.getTheme().resolveAttribute(layout, tempValue, true)) {
layout = tempValue.resourceId;
}
}
// ---- END CHANGES
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />", null, null);
} else {
Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a valid layout "
+ "reference. The layout ID " + value + " is not valid.", null, null);
}
} else {
final XmlResourceParser childParser =
thisInflater.getContext().getResources().getLayout(layout);
try {
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
while ((type = childParser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty.
}
if (type != XmlPullParser.START_TAG) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
childParser.getPositionDescription() + ": No start tag found!",
null, null);
LayoutInflater.consumeChildElements(parser);
return;
}
final String childName = childParser.getName();
if (TAG_MERGE.equals(childName)) {
// Inflate all children.
thisInflater.rInflate(childParser, parent, context, childAttrs, false);
} else {
final View view = thisInflater.createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.Include);
final int id = a.getResourceId(
com.android.internal.R.styleable.Include_id, View.NO_ID);
final int visibility = a.getInt(
com.android.internal.R.styleable.Include_visibility, -1);
a.recycle();
// 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 {
// ---- START CHANGES
sIsInInclude = true;
// ---- END CHANGES
params = group.generateLayoutParams(attrs);
} catch (RuntimeException ignored) {
// Ignore, just fail over to child attrs.
} finally {
// ---- START CHANGES
sIsInInclude = false;
// ---- END CHANGES
}
if (params == null) {
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
// Inflate all children.
thisInflater.rInflateChildren(childParser, view, childAttrs, true);
if (id != View.NO_ID) {
view.setId(id);
}
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
group.addView(view);
}
} finally {
childParser.close();
}
}
} else {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
"<include /> can only be used inside of a ViewGroup",
null, null);
}
LayoutInflater.consumeChildElements(parser);
}
@LayoutlibDelegate
/* package */ static void initPrecompiledViews(LayoutInflater thisInflater) {
initPrecompiledViews(thisInflater, false);
}
@LayoutlibDelegate
/* package */ static void initPrecompiledViews(LayoutInflater thisInflater,
boolean enablePrecompiledViews) {
thisInflater.initPrecompiledViews_Original(enablePrecompiledViews);
}
}