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 
17 package android.view;
18 
19 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
20 
21 import org.xmlpull.v1.XmlPullParser;
22 import org.xmlpull.v1.XmlPullParserException;
23 
24 import android.content.Context;
25 import android.content.res.TypedArray;
26 import android.content.res.XmlResourceParser;
27 import android.util.AttributeSet;
28 import android.util.TypedValue;
29 import android.util.Xml;
30 
31 import 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  */
40 public 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
rInflate(LayoutInflater thisInflater, XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate)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
parseInclude(LayoutInflater thisInflater, XmlPullParser parser, Context context, View parent, AttributeSet attrs)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