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