blob: 8192b5fbbf82f7a9e1ec7e57c4dcfeecd7b9d569 [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001/*
2 * Copyright (C) 2008 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.IProjectCallback;
20import com.android.ide.common.rendering.api.LayoutLog;
21import com.android.ide.common.rendering.api.MergeCookie;
22import com.android.ide.common.rendering.api.ResourceReference;
23import com.android.ide.common.rendering.api.ResourceValue;
24import com.android.layoutlib.bridge.Bridge;
Deepanshu Guptad345f442015-03-05 18:38:37 -080025import com.android.layoutlib.bridge.BridgeConstants;
Adam Lesinski282e1812014-01-23 18:17:42 -080026import com.android.layoutlib.bridge.android.BridgeContext;
27import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
Deepanshu Guptad345f442015-03-05 18:38:37 -080028import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
29import com.android.layoutlib.bridge.android.support.RecyclerViewUtil.LayoutManagerType;
Adam Lesinski282e1812014-01-23 18:17:42 -080030import com.android.layoutlib.bridge.impl.ParserFactory;
Deepanshu Guptad345f442015-03-05 18:38:37 -080031import com.android.layoutlib.bridge.impl.RenderSessionImpl;
Adam Lesinski282e1812014-01-23 18:17:42 -080032import com.android.resources.ResourceType;
33import com.android.util.Pair;
34
35import org.xmlpull.v1.XmlPullParser;
36
37import android.content.Context;
38import android.util.AttributeSet;
Adam Lesinski282e1812014-01-23 18:17:42 -080039
40import java.io.File;
41
42/**
43 * Custom implementation of {@link LayoutInflater} to handle custom views.
44 */
45public final class BridgeInflater extends LayoutInflater {
46
47 private final IProjectCallback mProjectCallback;
48 private boolean mIsInMerge = false;
49 private ResourceReference mResourceReference;
50
51 /**
52 * List of class prefixes which are tried first by default.
53 * <p/>
54 * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
55 */
56 private static final String[] sClassPrefixList = {
57 "android.widget.",
58 "android.webkit."
59 };
60
61 protected BridgeInflater(LayoutInflater original, Context newContext) {
62 super(original, newContext);
63 mProjectCallback = null;
64 }
65
66 /**
67 * Instantiate a new BridgeInflater with an {@link IProjectCallback} object.
68 *
69 * @param context The Android application context.
70 * @param projectCallback the {@link IProjectCallback} object.
71 */
72 public BridgeInflater(Context context, IProjectCallback projectCallback) {
73 super(context);
74 mProjectCallback = projectCallback;
75 mConstructorArgs[0] = context;
76 }
77
78 @Override
79 public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
80 View view = null;
81
82 try {
83 // First try to find a class using the default Android prefixes
84 for (String prefix : sClassPrefixList) {
85 try {
86 view = createView(name, prefix, attrs);
87 if (view != null) {
88 break;
89 }
90 } catch (ClassNotFoundException e) {
91 // Ignore. We'll try again using the base class below.
92 }
93 }
94
95 // Next try using the parent loader. This will most likely only work for
96 // fully-qualified class names.
97 try {
98 if (view == null) {
99 view = super.onCreateView(name, attrs);
100 }
101 } catch (ClassNotFoundException e) {
102 // Ignore. We'll try again using the custom view loader below.
103 }
104
105 // Finally try again using the custom view loader
106 try {
107 if (view == null) {
108 view = loadCustomView(name, attrs);
109 }
110 } catch (ClassNotFoundException e) {
111 // If the class was not found, we throw the exception directly, because this
112 // method is already expected to throw it.
113 throw e;
114 }
115 } catch (Exception e) {
116 // Wrap the real exception in a ClassNotFoundException, so that the calling method
117 // can deal with it.
Deepanshu Guptad345f442015-03-05 18:38:37 -0800118 throw new ClassNotFoundException("onCreateView", e);
Adam Lesinski282e1812014-01-23 18:17:42 -0800119 }
120
121 setupViewInContext(view, attrs);
122
123 return view;
124 }
125
126 @Override
Alan Viveretteebcef6b2014-01-08 15:15:01 -0800127 public View createViewFromTag(View parent, String name, AttributeSet attrs,
128 boolean inheritContext) {
Deepanshu Guptad345f442015-03-05 18:38:37 -0800129 View view;
Adam Lesinski282e1812014-01-23 18:17:42 -0800130 try {
Alan Viveretteebcef6b2014-01-08 15:15:01 -0800131 view = super.createViewFromTag(parent, name, attrs, inheritContext);
Adam Lesinski282e1812014-01-23 18:17:42 -0800132 } catch (InflateException e) {
133 // try to load the class from using the custom view loader
134 try {
135 view = loadCustomView(name, attrs);
136 } catch (Exception e2) {
137 // Wrap the real exception in an InflateException so that the calling
138 // method can deal with it.
139 InflateException exception = new InflateException();
Deepanshu Guptad345f442015-03-05 18:38:37 -0800140 if (!e2.getClass().equals(ClassNotFoundException.class)) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800141 exception.initCause(e2);
142 } else {
143 exception.initCause(e);
144 }
145 throw exception;
146 }
147 }
148
149 setupViewInContext(view, attrs);
150
151 return view;
152 }
153
154 @Override
155 public View inflate(int resource, ViewGroup root) {
156 Context context = getContext();
Deepanshu Guptab54b78e2014-06-09 19:55:41 -0700157 while (context instanceof ContextThemeWrapper) {
Deepanshu Gupta10019612014-04-18 12:32:38 -0700158 context = ((ContextThemeWrapper) context).getBaseContext();
159 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800160 if (context instanceof BridgeContext) {
161 BridgeContext bridgeContext = (BridgeContext)context;
162
163 ResourceValue value = null;
164
165 Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
166 if (layoutInfo != null) {
167 value = bridgeContext.getRenderResources().getFrameworkResource(
168 ResourceType.LAYOUT, layoutInfo.getSecond());
169 } else {
170 layoutInfo = mProjectCallback.resolveResourceId(resource);
171
172 if (layoutInfo != null) {
173 value = bridgeContext.getRenderResources().getProjectResource(
174 ResourceType.LAYOUT, layoutInfo.getSecond());
175 }
176 }
177
178 if (value != null) {
179 File f = new File(value.getValue());
180 if (f.isFile()) {
181 try {
182 XmlPullParser parser = ParserFactory.create(f);
183
184 BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
Deepanshu Gupta33c1c072015-02-23 09:18:08 -0800185 parser, bridgeContext, value.isFramework());
Adam Lesinski282e1812014-01-23 18:17:42 -0800186
187 return inflate(bridgeParser, root);
188 } catch (Exception e) {
189 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
Deepanshu Guptad345f442015-03-05 18:38:37 -0800190 "Failed to parse file " + f.getAbsolutePath(), e, null);
Adam Lesinski282e1812014-01-23 18:17:42 -0800191
192 return null;
193 }
194 }
195 }
196 }
197 return null;
198 }
199
Deepanshu Guptad345f442015-03-05 18:38:37 -0800200 private View loadCustomView(String name, AttributeSet attrs) throws Exception {
Adam Lesinski282e1812014-01-23 18:17:42 -0800201 if (mProjectCallback != null) {
202 // first get the classname in case it's not the node name
203 if (name.equals("view")) {
204 name = attrs.getAttributeValue(null, "class");
205 }
206
207 mConstructorArgs[1] = attrs;
208
209 Object customView = mProjectCallback.loadView(name, mConstructorSignature,
210 mConstructorArgs);
211
212 if (customView instanceof View) {
213 return (View)customView;
214 }
215 }
216
217 return null;
218 }
219
220 private void setupViewInContext(View view, AttributeSet attrs) {
Deepanshu Gupta10019612014-04-18 12:32:38 -0700221 Context context = getContext();
Deepanshu Guptab54b78e2014-06-09 19:55:41 -0700222 while (context instanceof ContextThemeWrapper) {
Deepanshu Gupta10019612014-04-18 12:32:38 -0700223 context = ((ContextThemeWrapper) context).getBaseContext();
224 }
225 if (context instanceof BridgeContext) {
226 BridgeContext bc = (BridgeContext) context;
227 // get the view key
228 Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
229 if (viewKey != null) {
230 bc.addViewKey(view, viewKey);
Adam Lesinski282e1812014-01-23 18:17:42 -0800231 }
Deepanshu Guptad345f442015-03-05 18:38:37 -0800232 if (RenderSessionImpl.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
233 String type = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES,
234 BridgeConstants.ATTR_LAYOUT_MANAGER_TYPE);
235 if (type != null) {
Deepanshu Gupta0412b312015-03-11 16:48:20 -0700236 LayoutManagerType layoutManagerType = LayoutManagerType.getByLogicalName(type);
Deepanshu Guptad345f442015-03-05 18:38:37 -0800237 if (layoutManagerType == null) {
Deepanshu Gupta0412b312015-03-11 16:48:20 -0700238 layoutManagerType = LayoutManagerType.getByClassName(type);
239 }
240 if (layoutManagerType == null) {
241 // add the classname itself.
242 bc.addCookie(view, type);
Deepanshu Guptad345f442015-03-05 18:38:37 -0800243 } else {
244 bc.addCookie(view, layoutManagerType);
245 }
246 }
247 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800248 }
249 }
250
251 public void setIsInMerge(boolean isInMerge) {
252 mIsInMerge = isInMerge;
253 }
254
255 public void setResourceReference(ResourceReference reference) {
256 mResourceReference = reference;
257 }
258
259 @Override
260 public LayoutInflater cloneInContext(Context newContext) {
261 return new BridgeInflater(this, newContext);
262 }
Deepanshu Gupta10019612014-04-18 12:32:38 -0700263
264 /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc,
265 ResourceReference resourceReference, boolean isInMerge) {
266
267 if (!(attrs instanceof BridgeXmlBlockParser)) {
268 return null;
269 }
270 BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs);
271
272 // get the view key
273 Object viewKey = parser.getViewCookie();
274
275 if (viewKey == null) {
276 int currentDepth = parser.getDepth();
277
278 // test whether we are in an included file or in a adapter binding view.
279 BridgeXmlBlockParser previousParser = bc.getPreviousParser();
280 if (previousParser != null) {
281 // looks like we are inside an embedded layout.
282 // only apply the cookie of the calling node (<include>) if we are at the
283 // top level of the embedded layout. If there is a merge tag, then
284 // skip it and look for the 2nd level
285 int testDepth = isInMerge ? 2 : 1;
286 if (currentDepth == testDepth) {
287 viewKey = previousParser.getViewCookie();
288 // if we are in a merge, wrap the cookie in a MergeCookie.
289 if (viewKey != null && isInMerge) {
290 viewKey = new MergeCookie(viewKey);
291 }
292 }
293 } else if (resourceReference != null && currentDepth == 1) {
294 // else if there's a resource reference, this means we are in an adapter
295 // binding case. Set the resource ref as the view cookie only for the top
296 // level view.
297 viewKey = resourceReference;
298 }
299 }
300
301 return viewKey;
302 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800303}