blob: cdbe2004cdea4acea665a181de82354a127d56fe [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;
25import com.android.layoutlib.bridge.android.BridgeContext;
26import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
27import com.android.layoutlib.bridge.impl.ParserFactory;
28import com.android.resources.ResourceType;
29import com.android.util.Pair;
30
31import org.xmlpull.v1.XmlPullParser;
32
33import android.content.Context;
34import android.util.AttributeSet;
35import android.view.InflateException;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39
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.
118 ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e);
119 throw exception;
120 }
121
122 setupViewInContext(view, attrs);
123
124 return view;
125 }
126
127 @Override
Alan Viveretteebcef6b2014-01-08 15:15:01 -0800128 public View createViewFromTag(View parent, String name, AttributeSet attrs,
129 boolean inheritContext) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800130 View view = null;
131 try {
Alan Viveretteebcef6b2014-01-08 15:15:01 -0800132 view = super.createViewFromTag(parent, name, attrs, inheritContext);
Adam Lesinski282e1812014-01-23 18:17:42 -0800133 } catch (InflateException e) {
134 // try to load the class from using the custom view loader
135 try {
136 view = loadCustomView(name, attrs);
137 } catch (Exception e2) {
138 // Wrap the real exception in an InflateException so that the calling
139 // method can deal with it.
140 InflateException exception = new InflateException();
141 if (e2.getClass().equals(ClassNotFoundException.class) == false) {
142 exception.initCause(e2);
143 } else {
144 exception.initCause(e);
145 }
146 throw exception;
147 }
148 }
149
150 setupViewInContext(view, attrs);
151
152 return view;
153 }
154
155 @Override
156 public View inflate(int resource, ViewGroup root) {
157 Context context = getContext();
158 if (context instanceof BridgeContext) {
159 BridgeContext bridgeContext = (BridgeContext)context;
160
161 ResourceValue value = null;
162
163 Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
164 if (layoutInfo != null) {
165 value = bridgeContext.getRenderResources().getFrameworkResource(
166 ResourceType.LAYOUT, layoutInfo.getSecond());
167 } else {
168 layoutInfo = mProjectCallback.resolveResourceId(resource);
169
170 if (layoutInfo != null) {
171 value = bridgeContext.getRenderResources().getProjectResource(
172 ResourceType.LAYOUT, layoutInfo.getSecond());
173 }
174 }
175
176 if (value != null) {
177 File f = new File(value.getValue());
178 if (f.isFile()) {
179 try {
180 XmlPullParser parser = ParserFactory.create(f);
181
182 BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
183 parser, bridgeContext, false);
184
185 return inflate(bridgeParser, root);
186 } catch (Exception e) {
187 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
188 "Failed to parse file " + f.getAbsolutePath(), e, null /*data*/);
189
190 return null;
191 }
192 }
193 }
194 }
195 return null;
196 }
197
198 private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException,
199 Exception{
200 if (mProjectCallback != null) {
201 // first get the classname in case it's not the node name
202 if (name.equals("view")) {
203 name = attrs.getAttributeValue(null, "class");
204 }
205
206 mConstructorArgs[1] = attrs;
207
208 Object customView = mProjectCallback.loadView(name, mConstructorSignature,
209 mConstructorArgs);
210
211 if (customView instanceof View) {
212 return (View)customView;
213 }
214 }
215
216 return null;
217 }
218
219 private void setupViewInContext(View view, AttributeSet attrs) {
220 if (getContext() instanceof BridgeContext) {
221 BridgeContext bc = (BridgeContext) getContext();
222 if (attrs instanceof BridgeXmlBlockParser) {
223 BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs;
224
225 // get the view key
226 Object viewKey = parser.getViewCookie();
227
228 if (viewKey == null) {
229 int currentDepth = parser.getDepth();
230
231 // test whether we are in an included file or in a adapter binding view.
232 BridgeXmlBlockParser previousParser = bc.getPreviousParser();
233 if (previousParser != null) {
234 // looks like we inside an embedded layout.
235 // only apply the cookie of the calling node (<include>) if we are at the
236 // top level of the embedded layout. If there is a merge tag, then
237 // skip it and look for the 2nd level
238 int testDepth = mIsInMerge ? 2 : 1;
239 if (currentDepth == testDepth) {
240 viewKey = previousParser.getViewCookie();
241 // if we are in a merge, wrap the cookie in a MergeCookie.
242 if (viewKey != null && mIsInMerge) {
243 viewKey = new MergeCookie(viewKey);
244 }
245 }
246 } else if (mResourceReference != null && currentDepth == 1) {
247 // else if there's a resource reference, this means we are in an adapter
248 // binding case. Set the resource ref as the view cookie only for the top
249 // level view.
250 viewKey = mResourceReference;
251 }
252 }
253
254 if (viewKey != null) {
255 bc.addViewKey(view, viewKey);
256 }
257 }
258 }
259 }
260
261 public void setIsInMerge(boolean isInMerge) {
262 mIsInMerge = isInMerge;
263 }
264
265 public void setResourceReference(ResourceReference reference) {
266 mResourceReference = reference;
267 }
268
269 @Override
270 public LayoutInflater cloneInContext(Context newContext) {
271 return new BridgeInflater(this, newContext);
272 }
273}