• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.layoutlib.bridge.android;
18 
19 import com.android.ide.common.rendering.api.IProjectCallback;
20 import com.android.ide.common.rendering.api.LayoutLog;
21 import com.android.ide.common.rendering.api.MergeCookie;
22 import com.android.ide.common.rendering.api.ResourceReference;
23 import com.android.ide.common.rendering.api.ResourceValue;
24 import com.android.layoutlib.bridge.Bridge;
25 import com.android.resources.ResourceType;
26 import com.android.util.Pair;
27 
28 import org.kxml2.io.KXmlParser;
29 import org.xmlpull.v1.XmlPullParser;
30 
31 import android.content.Context;
32 import android.util.AttributeSet;
33 import android.view.InflateException;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.ViewGroup;
37 
38 import java.io.File;
39 import java.io.FileInputStream;
40 
41 /**
42  * Custom implementation of {@link LayoutInflater} to handle custom views.
43  */
44 public final class BridgeInflater extends LayoutInflater {
45 
46     private final IProjectCallback mProjectCallback;
47     private boolean mIsInMerge = false;
48     private ResourceReference mResourceReference;
49 
50     /**
51      * List of class prefixes which are tried first by default.
52      * <p/>
53      * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
54      */
55     private static final String[] sClassPrefixList = {
56         "android.widget.",
57         "android.webkit."
58     };
59 
BridgeInflater(LayoutInflater original, Context newContext)60     protected BridgeInflater(LayoutInflater original, Context newContext) {
61         super(original, newContext);
62         mProjectCallback = null;
63     }
64 
65     /**
66      * Instantiate a new BridgeInflater with an {@link IProjectCallback} object.
67      *
68      * @param context The Android application context.
69      * @param projectCallback the {@link IProjectCallback} object.
70      */
BridgeInflater(Context context, IProjectCallback projectCallback)71     public BridgeInflater(Context context, IProjectCallback projectCallback) {
72         super(context);
73         mProjectCallback = projectCallback;
74         mConstructorArgs[0] = context;
75     }
76 
77     @Override
onCreateView(String name, AttributeSet attrs)78     public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
79         View view = null;
80 
81         try {
82             // First try to find a class using the default Android prefixes
83             for (String prefix : sClassPrefixList) {
84                 try {
85                     view = createView(name, prefix, attrs);
86                     if (view != null) {
87                         break;
88                     }
89                 } catch (ClassNotFoundException e) {
90                     // Ignore. We'll try again using the base class below.
91                 }
92             }
93 
94             // Next try using the parent loader. This will most likely only work for
95             // fully-qualified class names.
96             try {
97                 if (view == null) {
98                     view = super.onCreateView(name, attrs);
99                 }
100             } catch (ClassNotFoundException e) {
101                 // Ignore. We'll try again using the custom view loader below.
102             }
103 
104             // Finally try again using the custom view loader
105             try {
106                 if (view == null) {
107                     view = loadCustomView(name, attrs);
108                 }
109             } catch (ClassNotFoundException e) {
110                 // If the class was not found, we throw the exception directly, because this
111                 // method is already expected to throw it.
112                 throw e;
113             }
114         } catch (Exception e) {
115             // Wrap the real exception in a ClassNotFoundException, so that the calling method
116             // can deal with it.
117             ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e);
118             throw exception;
119         }
120 
121         setupViewInContext(view, attrs);
122 
123         return view;
124     }
125 
126     @Override
createViewFromTag(String name, AttributeSet attrs)127     public View createViewFromTag(String name, AttributeSet attrs) {
128         View view = null;
129         try {
130             view = super.createViewFromTag(name, attrs);
131         } catch (InflateException e) {
132             // try to load the class from using the custom view loader
133             try {
134                 view = loadCustomView(name, attrs);
135             } catch (Exception e2) {
136                 // Wrap the real exception in an InflateException so that the calling
137                 // method can deal with it.
138                 InflateException exception = new InflateException();
139                 if (e2.getClass().equals(ClassNotFoundException.class) == false) {
140                     exception.initCause(e2);
141                 } else {
142                     exception.initCause(e);
143                 }
144                 throw exception;
145             }
146         }
147 
148         setupViewInContext(view, attrs);
149 
150         return view;
151     }
152 
153     @Override
inflate(int resource, ViewGroup root)154     public View inflate(int resource, ViewGroup root) {
155         Context context = getContext();
156         if (context instanceof BridgeContext) {
157             BridgeContext bridgeContext = (BridgeContext)context;
158 
159             ResourceValue value = null;
160 
161             Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
162             if (layoutInfo != null) {
163                 value = bridgeContext.getRenderResources().getFrameworkResource(
164                         ResourceType.LAYOUT, layoutInfo.getSecond());
165             } else {
166                 layoutInfo = mProjectCallback.resolveResourceId(resource);
167 
168                 if (layoutInfo != null) {
169                     value = bridgeContext.getRenderResources().getProjectResource(
170                             ResourceType.LAYOUT, layoutInfo.getSecond());
171                 }
172             }
173 
174             if (value != null) {
175                 File f = new File(value.getValue());
176                 if (f.isFile()) {
177                     try {
178                         KXmlParser parser = new KXmlParser();
179                         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
180                         parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$
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 
loadCustomView(String name, AttributeSet attrs)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 
setupViewInContext(View view, AttributeSet attrs)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 
setIsInMerge(boolean isInMerge)261     public void setIsInMerge(boolean isInMerge) {
262         mIsInMerge = isInMerge;
263     }
264 
setResourceReference(ResourceReference reference)265     public void setResourceReference(ResourceReference reference) {
266         mResourceReference = reference;
267     }
268 
269     @Override
cloneInContext(Context newContext)270     public LayoutInflater cloneInContext(Context newContext) {
271         return new BridgeInflater(this, newContext);
272     }
273 }
274