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.ide.common.rendering.api.LayoutLog;
20 import com.android.layoutlib.bridge.Bridge;
21 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
22 
23 import org.xmlpull.v1.XmlPullParser;
24 import org.xmlpull.v1.XmlPullParserException;
25 
26 import android.content.Context;
27 import android.content.res.TypedArray;
28 import android.content.res.XmlResourceParser;
29 import android.util.AttributeSet;
30 import android.util.TypedValue;
31 import android.util.Xml;
32 
33 import 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  */
42 public 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
rInflate(LayoutInflater thisInflater, XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate)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
parseInclude(LayoutInflater thisInflater, XmlPullParser parser, Context context, View parent, AttributeSet attrs)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, 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, 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, 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, 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, null);
232         }
233 
234         LayoutInflater.consumeChildElements(parser);
235     }
236 
237     @LayoutlibDelegate
initPrecompiledViews(LayoutInflater thisInflater)238     /* package */ static void initPrecompiledViews(LayoutInflater thisInflater) {
239         initPrecompiledViews(thisInflater, false);
240     }
241 
242     @LayoutlibDelegate
initPrecompiledViews(LayoutInflater thisInflater, boolean enablePrecompiledViews)243     /* package */ static void initPrecompiledViews(LayoutInflater thisInflater,
244             boolean enablePrecompiledViews) {
245         thisInflater.initPrecompiledViews_Original(enablePrecompiledViews);
246     }
247 }
248