blob: 1e33e3ab2090c6bc112e678a2c6aa94ce1334fb7 [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
Adam Lesinski282e1812014-01-23 18:17:42 -080019import com.android.ide.common.rendering.api.LayoutLog;
Deepanshu Gupta2bc2daa2015-03-30 15:01:03 -070020import com.android.ide.common.rendering.api.LayoutlibCallback;
Adam Lesinski282e1812014-01-23 18:17:42 -080021import 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;
Jens Ole Lauridsen4dfe4d42015-06-05 08:04:27 -070025import 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 Guptaccbc1172015-07-10 17:38:29 -070028import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
Deepanshu Gupta61f23e92015-07-06 18:31:20 -070029import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
Adam Lesinski282e1812014-01-23 18:17:42 -080030import com.android.layoutlib.bridge.impl.ParserFactory;
Deepanshu Gupta61f23e92015-07-06 18:31:20 -070031import com.android.layoutlib.bridge.util.ReflectionUtils;
Adam Lesinski282e1812014-01-23 18:17:42 -080032import com.android.resources.ResourceType;
33import com.android.util.Pair;
34
35import org.xmlpull.v1.XmlPullParser;
36
Deepanshu Guptaccbc1172015-07-10 17:38:29 -070037import android.annotation.NonNull;
Adam Lesinski282e1812014-01-23 18:17:42 -080038import android.content.Context;
39import android.util.AttributeSet;
Adam Lesinski282e1812014-01-23 18:17:42 -080040
41import java.io.File;
Deepanshu Guptaccbc1172015-07-10 17:38:29 -070042import java.util.HashMap;
43import java.util.Map;
Adam Lesinski282e1812014-01-23 18:17:42 -080044
Deepanshu Guptaada85902015-03-13 11:06:47 -070045import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
46
Adam Lesinski282e1812014-01-23 18:17:42 -080047/**
48 * Custom implementation of {@link LayoutInflater} to handle custom views.
49 */
50public final class BridgeInflater extends LayoutInflater {
51
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -070052 private final LayoutlibCallback mLayoutlibCallback;
Adam Lesinski282e1812014-01-23 18:17:42 -080053 private boolean mIsInMerge = false;
54 private ResourceReference mResourceReference;
Deepanshu Guptaccbc1172015-07-10 17:38:29 -070055 private Map<View, String> mOpenDrawerLayouts;
Adam Lesinski282e1812014-01-23 18:17:42 -080056
57 /**
58 * List of class prefixes which are tried first by default.
59 * <p/>
60 * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
61 */
62 private static final String[] sClassPrefixList = {
63 "android.widget.",
Deepanshu Guptaf8ea7502015-05-18 18:47:07 -070064 "android.webkit.",
65 "android.app."
Adam Lesinski282e1812014-01-23 18:17:42 -080066 };
67
Deepanshu Guptaf8ea7502015-05-18 18:47:07 -070068 public static String[] getClassPrefixList() {
69 return sClassPrefixList;
70 }
71
Adam Lesinski282e1812014-01-23 18:17:42 -080072 protected BridgeInflater(LayoutInflater original, Context newContext) {
73 super(original, newContext);
Deepanshu Guptaada85902015-03-13 11:06:47 -070074 newContext = getBaseContext(newContext);
75 if (newContext instanceof BridgeContext) {
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -070076 mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback();
Deepanshu Guptaada85902015-03-13 11:06:47 -070077 } else {
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -070078 mLayoutlibCallback = null;
Deepanshu Guptaada85902015-03-13 11:06:47 -070079 }
Adam Lesinski282e1812014-01-23 18:17:42 -080080 }
81
82 /**
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -070083 * Instantiate a new BridgeInflater with an {@link LayoutlibCallback} object.
Adam Lesinski282e1812014-01-23 18:17:42 -080084 *
85 * @param context The Android application context.
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -070086 * @param layoutlibCallback the {@link LayoutlibCallback} object.
Adam Lesinski282e1812014-01-23 18:17:42 -080087 */
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -070088 public BridgeInflater(Context context, LayoutlibCallback layoutlibCallback) {
Adam Lesinski282e1812014-01-23 18:17:42 -080089 super(context);
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -070090 mLayoutlibCallback = layoutlibCallback;
Adam Lesinski282e1812014-01-23 18:17:42 -080091 mConstructorArgs[0] = context;
92 }
93
94 @Override
95 public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
96 View view = null;
97
98 try {
99 // First try to find a class using the default Android prefixes
100 for (String prefix : sClassPrefixList) {
101 try {
102 view = createView(name, prefix, attrs);
103 if (view != null) {
104 break;
105 }
106 } catch (ClassNotFoundException e) {
107 // Ignore. We'll try again using the base class below.
108 }
109 }
110
111 // Next try using the parent loader. This will most likely only work for
112 // fully-qualified class names.
113 try {
114 if (view == null) {
115 view = super.onCreateView(name, attrs);
116 }
117 } catch (ClassNotFoundException e) {
118 // Ignore. We'll try again using the custom view loader below.
119 }
120
121 // Finally try again using the custom view loader
Deepanshu Gupta61f23e92015-07-06 18:31:20 -0700122 if (view == null) {
123 view = loadCustomView(name, attrs);
Adam Lesinski282e1812014-01-23 18:17:42 -0800124 }
125 } catch (Exception e) {
126 // Wrap the real exception in a ClassNotFoundException, so that the calling method
127 // can deal with it.
Deepanshu Guptad345f442015-03-05 18:38:37 -0800128 throw new ClassNotFoundException("onCreateView", e);
Adam Lesinski282e1812014-01-23 18:17:42 -0800129 }
130
131 setupViewInContext(view, attrs);
132
133 return view;
134 }
135
136 @Override
Alan Viverette6194d722015-03-20 15:49:06 -0700137 public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
138 boolean ignoreThemeAttrs) {
Deepanshu Guptad345f442015-03-05 18:38:37 -0800139 View view;
Adam Lesinski282e1812014-01-23 18:17:42 -0800140 try {
Alan Viverette6194d722015-03-20 15:49:06 -0700141 view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttrs);
Adam Lesinski282e1812014-01-23 18:17:42 -0800142 } catch (InflateException e) {
143 // try to load the class from using the custom view loader
144 try {
145 view = loadCustomView(name, attrs);
146 } catch (Exception e2) {
147 // Wrap the real exception in an InflateException so that the calling
148 // method can deal with it.
149 InflateException exception = new InflateException();
Deepanshu Guptad345f442015-03-05 18:38:37 -0800150 if (!e2.getClass().equals(ClassNotFoundException.class)) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800151 exception.initCause(e2);
152 } else {
153 exception.initCause(e);
154 }
155 throw exception;
156 }
157 }
158
159 setupViewInContext(view, attrs);
160
161 return view;
162 }
163
164 @Override
165 public View inflate(int resource, ViewGroup root) {
166 Context context = getContext();
Deepanshu Guptaada85902015-03-13 11:06:47 -0700167 context = getBaseContext(context);
Adam Lesinski282e1812014-01-23 18:17:42 -0800168 if (context instanceof BridgeContext) {
169 BridgeContext bridgeContext = (BridgeContext)context;
170
171 ResourceValue value = null;
172
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700173 @SuppressWarnings("deprecation")
Adam Lesinski282e1812014-01-23 18:17:42 -0800174 Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
175 if (layoutInfo != null) {
176 value = bridgeContext.getRenderResources().getFrameworkResource(
177 ResourceType.LAYOUT, layoutInfo.getSecond());
178 } else {
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700179 layoutInfo = mLayoutlibCallback.resolveResourceId(resource);
Adam Lesinski282e1812014-01-23 18:17:42 -0800180
181 if (layoutInfo != null) {
182 value = bridgeContext.getRenderResources().getProjectResource(
183 ResourceType.LAYOUT, layoutInfo.getSecond());
184 }
185 }
186
187 if (value != null) {
188 File f = new File(value.getValue());
189 if (f.isFile()) {
190 try {
191 XmlPullParser parser = ParserFactory.create(f);
192
193 BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
Deepanshu Gupta33c1c072015-02-23 09:18:08 -0800194 parser, bridgeContext, value.isFramework());
Adam Lesinski282e1812014-01-23 18:17:42 -0800195
196 return inflate(bridgeParser, root);
197 } catch (Exception e) {
198 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
Deepanshu Guptad345f442015-03-05 18:38:37 -0800199 "Failed to parse file " + f.getAbsolutePath(), e, null);
Adam Lesinski282e1812014-01-23 18:17:42 -0800200
201 return null;
202 }
203 }
204 }
205 }
206 return null;
207 }
208
Deepanshu Guptad345f442015-03-05 18:38:37 -0800209 private View loadCustomView(String name, AttributeSet attrs) throws Exception {
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700210 if (mLayoutlibCallback != null) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800211 // first get the classname in case it's not the node name
212 if (name.equals("view")) {
213 name = attrs.getAttributeValue(null, "class");
214 }
215
216 mConstructorArgs[1] = attrs;
217
Deepanshu Gupta37dbb8b2015-04-14 16:39:41 -0700218 Object customView = mLayoutlibCallback.loadView(name, mConstructorSignature,
Adam Lesinski282e1812014-01-23 18:17:42 -0800219 mConstructorArgs);
220
221 if (customView instanceof View) {
222 return (View)customView;
223 }
224 }
225
226 return null;
227 }
228
229 private void setupViewInContext(View view, AttributeSet attrs) {
Deepanshu Gupta10019612014-04-18 12:32:38 -0700230 Context context = getContext();
Deepanshu Guptaada85902015-03-13 11:06:47 -0700231 context = getBaseContext(context);
Deepanshu Gupta10019612014-04-18 12:32:38 -0700232 if (context instanceof BridgeContext) {
233 BridgeContext bc = (BridgeContext) context;
234 // get the view key
235 Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
236 if (viewKey != null) {
237 bc.addViewKey(view, viewKey);
Adam Lesinski282e1812014-01-23 18:17:42 -0800238 }
Jens Ole Lauridsen4dfe4d42015-06-05 08:04:27 -0700239 String scrollPos = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
240 if (scrollPos != null) {
241 if (scrollPos.endsWith("px")) {
242 int value = Integer.parseInt(scrollPos.substring(0, scrollPos.length() - 2));
243 bc.setScrollYPos(view, value);
244 }
245 }
Deepanshu Gupta61f23e92015-07-06 18:31:20 -0700246 if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
247 Integer resourceId = null;
248 String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
249 BridgeConstants.ATTR_LIST_ITEM);
250 if (attrVal != null && !attrVal.isEmpty()) {
251 ResourceValue resValue = bc.getRenderResources().findResValue(attrVal, false);
252 if (resValue.isFramework()) {
253 resourceId = Bridge.getResourceId(resValue.getResourceType(),
254 resValue.getName());
255 } else {
256 resourceId = mLayoutlibCallback.getResourceId(resValue.getResourceType(),
257 resValue.getName());
258 }
259 }
260 if (resourceId == null) {
261 resourceId = 0;
262 }
263 RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId);
Deepanshu Guptaccbc1172015-07-10 17:38:29 -0700264 } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) {
265 String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
266 BridgeConstants.ATTR_OPEN_DRAWER);
267 if (attrVal != null) {
268 getDrawerLayoutMap().put(view, attrVal);
269 }
Deepanshu Gupta61f23e92015-07-06 18:31:20 -0700270 }
Deepanshu Guptaccbc1172015-07-10 17:38:29 -0700271
Adam Lesinski282e1812014-01-23 18:17:42 -0800272 }
273 }
274
275 public void setIsInMerge(boolean isInMerge) {
276 mIsInMerge = isInMerge;
277 }
278
279 public void setResourceReference(ResourceReference reference) {
280 mResourceReference = reference;
281 }
282
283 @Override
284 public LayoutInflater cloneInContext(Context newContext) {
285 return new BridgeInflater(this, newContext);
286 }
Deepanshu Gupta10019612014-04-18 12:32:38 -0700287
288 /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc,
289 ResourceReference resourceReference, boolean isInMerge) {
290
291 if (!(attrs instanceof BridgeXmlBlockParser)) {
292 return null;
293 }
294 BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs);
295
296 // get the view key
297 Object viewKey = parser.getViewCookie();
298
299 if (viewKey == null) {
300 int currentDepth = parser.getDepth();
301
302 // test whether we are in an included file or in a adapter binding view.
303 BridgeXmlBlockParser previousParser = bc.getPreviousParser();
304 if (previousParser != null) {
305 // looks like we are inside an embedded layout.
306 // only apply the cookie of the calling node (<include>) if we are at the
307 // top level of the embedded layout. If there is a merge tag, then
308 // skip it and look for the 2nd level
309 int testDepth = isInMerge ? 2 : 1;
310 if (currentDepth == testDepth) {
311 viewKey = previousParser.getViewCookie();
312 // if we are in a merge, wrap the cookie in a MergeCookie.
313 if (viewKey != null && isInMerge) {
314 viewKey = new MergeCookie(viewKey);
315 }
316 }
317 } else if (resourceReference != null && currentDepth == 1) {
318 // else if there's a resource reference, this means we are in an adapter
319 // binding case. Set the resource ref as the view cookie only for the top
320 // level view.
321 viewKey = resourceReference;
322 }
323 }
324
325 return viewKey;
326 }
Deepanshu Guptaccbc1172015-07-10 17:38:29 -0700327
328 public void postInflateProcess(View view) {
329 if (mOpenDrawerLayouts != null) {
330 String gravity = mOpenDrawerLayouts.get(view);
331 if (gravity != null) {
332 DrawerLayoutUtil.openDrawer(view, gravity);
333 }
334 mOpenDrawerLayouts.remove(view);
335 }
336 }
337
338 @NonNull
339 private Map<View, String> getDrawerLayoutMap() {
340 if (mOpenDrawerLayouts == null) {
341 mOpenDrawerLayouts = new HashMap<View, String>(4);
342 }
343 return mOpenDrawerLayouts;
344 }
345
346 public void onDoneInflation() {
347 if (mOpenDrawerLayouts != null) {
348 mOpenDrawerLayouts.clear();
349 }
350 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800351}