• 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 android.view;
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.layoutlib.bridge.android.BridgeContext;
26 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
27 import com.android.layoutlib.bridge.impl.ParserFactory;
28 import com.android.resources.ResourceType;
29 import com.android.util.Pair;
30 
31 import org.xmlpull.v1.XmlPullParser;
32 
33 import android.content.Context;
34 import android.util.AttributeSet;
35 import android.view.InflateException;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 
40 import java.io.File;
41 
42 /**
43  * Custom implementation of {@link LayoutInflater} to handle custom views.
44  */
45 public 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 
BridgeInflater(LayoutInflater original, Context newContext)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      */
BridgeInflater(Context context, IProjectCallback projectCallback)72     public BridgeInflater(Context context, IProjectCallback projectCallback) {
73         super(context);
74         mProjectCallback = projectCallback;
75         mConstructorArgs[0] = context;
76     }
77 
78     @Override
onCreateView(String name, AttributeSet attrs)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
createViewFromTag(View parent, String name, AttributeSet attrs)128     public View createViewFromTag(View parent, String name, AttributeSet attrs) {
129         View view = null;
130         try {
131             view = super.createViewFromTag(parent, name, attrs);
132         } 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();
140                 if (e2.getClass().equals(ClassNotFoundException.class) == false) {
141                     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
inflate(int resource, ViewGroup root)155     public View inflate(int resource, ViewGroup root) {
156         Context context = getContext();
157         if (context instanceof BridgeContext) {
158             BridgeContext bridgeContext = (BridgeContext)context;
159 
160             ResourceValue value = null;
161 
162             Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
163             if (layoutInfo != null) {
164                 value = bridgeContext.getRenderResources().getFrameworkResource(
165                         ResourceType.LAYOUT, layoutInfo.getSecond());
166             } else {
167                 layoutInfo = mProjectCallback.resolveResourceId(resource);
168 
169                 if (layoutInfo != null) {
170                     value = bridgeContext.getRenderResources().getProjectResource(
171                             ResourceType.LAYOUT, layoutInfo.getSecond());
172                 }
173             }
174 
175             if (value != null) {
176                 File f = new File(value.getValue());
177                 if (f.isFile()) {
178                     try {
179                         XmlPullParser parser = ParserFactory.create(f);
180 
181                         BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
182                                 parser, bridgeContext, false);
183 
184                         return inflate(bridgeParser, root);
185                     } catch (Exception e) {
186                         Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
187                                 "Failed to parse file " + f.getAbsolutePath(), e, null /*data*/);
188 
189                         return null;
190                     }
191                 }
192             }
193         }
194         return null;
195     }
196 
loadCustomView(String name, AttributeSet attrs)197     private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException,
198             Exception{
199         if (mProjectCallback != null) {
200             // first get the classname in case it's not the node name
201             if (name.equals("view")) {
202                 name = attrs.getAttributeValue(null, "class");
203             }
204 
205             mConstructorArgs[1] = attrs;
206 
207             Object customView = mProjectCallback.loadView(name, mConstructorSignature,
208                     mConstructorArgs);
209 
210             if (customView instanceof View) {
211                 return (View)customView;
212             }
213         }
214 
215         return null;
216     }
217 
setupViewInContext(View view, AttributeSet attrs)218     private void setupViewInContext(View view, AttributeSet attrs) {
219         if (getContext() instanceof BridgeContext) {
220             BridgeContext bc = (BridgeContext) getContext();
221             if (attrs instanceof BridgeXmlBlockParser) {
222                 BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs;
223 
224                 // get the view key
225                 Object viewKey = parser.getViewCookie();
226 
227                 if (viewKey == null) {
228                     int currentDepth = parser.getDepth();
229 
230                     // test whether we are in an included file or in a adapter binding view.
231                     BridgeXmlBlockParser previousParser = bc.getPreviousParser();
232                     if (previousParser != null) {
233                         // looks like we inside an embedded layout.
234                         // only apply the cookie of the calling node (<include>) if we are at the
235                         // top level of the embedded layout. If there is a merge tag, then
236                         // skip it and look for the 2nd level
237                         int testDepth = mIsInMerge ? 2 : 1;
238                         if (currentDepth == testDepth) {
239                             viewKey = previousParser.getViewCookie();
240                             // if we are in a merge, wrap the cookie in a MergeCookie.
241                             if (viewKey != null && mIsInMerge) {
242                                 viewKey = new MergeCookie(viewKey);
243                             }
244                         }
245                     } else if (mResourceReference != null && currentDepth == 1) {
246                         // else if there's a resource reference, this means we are in an adapter
247                         // binding case. Set the resource ref as the view cookie only for the top
248                         // level view.
249                         viewKey = mResourceReference;
250                     }
251                 }
252 
253                 if (viewKey != null) {
254                     bc.addViewKey(view, viewKey);
255                 }
256             }
257         }
258     }
259 
setIsInMerge(boolean isInMerge)260     public void setIsInMerge(boolean isInMerge) {
261         mIsInMerge = isInMerge;
262     }
263 
setResourceReference(ResourceReference reference)264     public void setResourceReference(ResourceReference reference) {
265         mResourceReference = reference;
266     }
267 
268     @Override
cloneInContext(Context newContext)269     public LayoutInflater cloneInContext(Context newContext) {
270         return new BridgeInflater(this, newContext);
271     }
272 }
273