1/*
2 * Copyright (C) 2011 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
17package android.view;
18
19import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
20
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.content.Context;
25import android.content.res.TypedArray;
26import android.content.res.XmlResourceParser;
27import android.util.AttributeSet;
28import android.util.TypedValue;
29import android.util.Xml;
30
31import java.io.IOException;
32
33/**
34 * Delegate used to provide new implementation of a select few methods of {@link LayoutInflater}
35 *
36 * Through the layoutlib_create tool, the original  methods of LayoutInflater have been replaced
37 * by calls to methods of the same name in this delegate class.
38 *
39 */
40public class LayoutInflater_Delegate {
41    private static final String TAG_MERGE = "merge";
42
43    private static final String ATTR_LAYOUT = "layout";
44
45    private static final int[] ATTRS_THEME = new int[] {
46            com.android.internal.R.attr.theme };
47
48    public static boolean sIsInInclude = false;
49
50    /**
51     * Recursive method used to descend down the xml hierarchy and instantiate
52     * views, instantiate their children, and then call onFinishInflate().
53     *
54     * This implementation just records the merge status before calling the default implementation.
55     */
56    @LayoutlibDelegate
57    /* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser,
58            View parent, Context context, AttributeSet attrs, boolean finishInflate)
59            throws XmlPullParserException, IOException {
60
61        if (finishInflate == false) {
62            // this is a merge rInflate!
63            if (thisInflater instanceof BridgeInflater) {
64                ((BridgeInflater) thisInflater).setIsInMerge(true);
65            }
66        }
67
68        // ---- START DEFAULT IMPLEMENTATION.
69
70        thisInflater.rInflate_Original(parser, parent, context, attrs, finishInflate);
71
72        // ---- END DEFAULT IMPLEMENTATION.
73
74        if (finishInflate == false) {
75            // this is a merge rInflate!
76            if (thisInflater instanceof BridgeInflater) {
77                ((BridgeInflater) thisInflater).setIsInMerge(false);
78            }
79        }
80    }
81
82    @LayoutlibDelegate
83    public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser,
84            Context context, View parent, AttributeSet attrs)
85            throws XmlPullParserException, IOException {
86        int type;
87
88        if (parent instanceof ViewGroup) {
89            // Apply a theme wrapper, if requested. This is sort of a weird
90            // edge case, since developers think the <include> overwrites
91            // values in the AttributeSet of the included View. So, if the
92            // included View has a theme attribute, we'll need to ignore it.
93            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
94            final int themeResId = ta.getResourceId(0, 0);
95            final boolean hasThemeOverride = themeResId != 0;
96            if (hasThemeOverride) {
97                context = new ContextThemeWrapper(context, themeResId);
98            }
99            ta.recycle();
100
101            // If the layout is pointing to a theme attribute, we have to
102            // massage the value to get a resource identifier out of it.
103            int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
104            if (layout == 0) {
105                final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
106                if (value == null || value.length() <= 0) {
107                    throw new InflateException("You must specify a layout in the"
108                            + " include tag: <include layout=\"@layout/layoutID\" />");
109                }
110
111                // Attempt to resolve the "?attr/name" string to an identifier.
112                layout = context.getResources().getIdentifier(value.substring(1), null, null);
113            }
114
115            // The layout might be referencing a theme attribute.
116            // ---- START CHANGES
117            if (layout != 0) {
118                final TypedValue tempValue = new TypedValue();
119                if (context.getTheme().resolveAttribute(layout, tempValue, true)) {
120                    layout = tempValue.resourceId;
121                }
122            }
123            // ---- END CHANGES
124
125            if (layout == 0) {
126                final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
127                if (value == null) {
128                    throw new InflateException("You must specifiy a layout in the"
129                            + " include tag: <include layout=\"@layout/layoutID\" />");
130                } else {
131                    throw new InflateException("You must specifiy a valid layout "
132                            + "reference. The layout ID " + value + " is not valid.");
133                }
134            } else {
135                final XmlResourceParser childParser =
136                    thisInflater.getContext().getResources().getLayout(layout);
137
138                try {
139                    final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
140
141                    while ((type = childParser.next()) != XmlPullParser.START_TAG &&
142                            type != XmlPullParser.END_DOCUMENT) {
143                        // Empty.
144                    }
145
146                    if (type != XmlPullParser.START_TAG) {
147                        throw new InflateException(childParser.getPositionDescription() +
148                                ": No start tag found!");
149                    }
150
151                    final String childName = childParser.getName();
152
153                    if (TAG_MERGE.equals(childName)) {
154                        // Inflate all children.
155                        thisInflater.rInflate(childParser, parent, context, childAttrs, false);
156                    } else {
157                        final View view = thisInflater.createViewFromTag(parent, childName,
158                                context, childAttrs, hasThemeOverride);
159                        final ViewGroup group = (ViewGroup) parent;
160
161                        final TypedArray a = context.obtainStyledAttributes(
162                                attrs, com.android.internal.R.styleable.Include);
163                        final int id = a.getResourceId(
164                                com.android.internal.R.styleable.Include_id, View.NO_ID);
165                        final int visibility = a.getInt(
166                                com.android.internal.R.styleable.Include_visibility, -1);
167                        a.recycle();
168
169                        // We try to load the layout params set in the <include /> tag. If
170                        // they don't exist, we will rely on the layout params set in the
171                        // included XML file.
172                        // During a layoutparams generation, a runtime exception is thrown
173                        // if either layout_width or layout_height is missing. We catch
174                        // this exception and set localParams accordingly: true means we
175                        // successfully loaded layout params from the <include /> tag,
176                        // false means we need to rely on the included layout params.
177                        ViewGroup.LayoutParams params = null;
178                        try {
179                            // ---- START CHANGES
180                            sIsInInclude = true;
181                            // ---- END CHANGES
182
183                            params = group.generateLayoutParams(attrs);
184                        } catch (RuntimeException ignored) {
185                            // Ignore, just fail over to child attrs.
186                        } finally {
187                            // ---- START CHANGES
188                            sIsInInclude = false;
189                            // ---- END CHANGES
190                        }
191                        if (params == null) {
192                            params = group.generateLayoutParams(childAttrs);
193                        }
194                        view.setLayoutParams(params);
195
196                        // Inflate all children.
197                        thisInflater.rInflateChildren(childParser, view, childAttrs, true);
198
199                        if (id != View.NO_ID) {
200                            view.setId(id);
201                        }
202
203                        switch (visibility) {
204                            case 0:
205                                view.setVisibility(View.VISIBLE);
206                                break;
207                            case 1:
208                                view.setVisibility(View.INVISIBLE);
209                                break;
210                            case 2:
211                                view.setVisibility(View.GONE);
212                                break;
213                        }
214
215                        group.addView(view);
216                    }
217                } finally {
218                    childParser.close();
219                }
220            }
221        } else {
222            throw new InflateException("<include /> can only be used inside of a ViewGroup");
223        }
224
225        LayoutInflater.consumeChildElements(parser);
226    }
227}
228