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 36 import java.io.File; 37 38 /** 39 * Custom implementation of {@link LayoutInflater} to handle custom views. 40 */ 41 public final class BridgeInflater extends LayoutInflater { 42 43 private final IProjectCallback mProjectCallback; 44 private boolean mIsInMerge = false; 45 private ResourceReference mResourceReference; 46 47 /** 48 * List of class prefixes which are tried first by default. 49 * <p/> 50 * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater. 51 */ 52 private static final String[] sClassPrefixList = { 53 "android.widget.", 54 "android.webkit." 55 }; 56 BridgeInflater(LayoutInflater original, Context newContext)57 protected BridgeInflater(LayoutInflater original, Context newContext) { 58 super(original, newContext); 59 mProjectCallback = null; 60 } 61 62 /** 63 * Instantiate a new BridgeInflater with an {@link IProjectCallback} object. 64 * 65 * @param context The Android application context. 66 * @param projectCallback the {@link IProjectCallback} object. 67 */ BridgeInflater(Context context, IProjectCallback projectCallback)68 public BridgeInflater(Context context, IProjectCallback projectCallback) { 69 super(context); 70 mProjectCallback = projectCallback; 71 mConstructorArgs[0] = context; 72 } 73 74 @Override onCreateView(String name, AttributeSet attrs)75 public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { 76 View view = null; 77 78 try { 79 // First try to find a class using the default Android prefixes 80 for (String prefix : sClassPrefixList) { 81 try { 82 view = createView(name, prefix, attrs); 83 if (view != null) { 84 break; 85 } 86 } catch (ClassNotFoundException e) { 87 // Ignore. We'll try again using the base class below. 88 } 89 } 90 91 // Next try using the parent loader. This will most likely only work for 92 // fully-qualified class names. 93 try { 94 if (view == null) { 95 view = super.onCreateView(name, attrs); 96 } 97 } catch (ClassNotFoundException e) { 98 // Ignore. We'll try again using the custom view loader below. 99 } 100 101 // Finally try again using the custom view loader 102 try { 103 if (view == null) { 104 view = loadCustomView(name, attrs); 105 } 106 } catch (ClassNotFoundException e) { 107 // If the class was not found, we throw the exception directly, because this 108 // method is already expected to throw it. 109 throw e; 110 } 111 } catch (Exception e) { 112 // Wrap the real exception in a ClassNotFoundException, so that the calling method 113 // can deal with it. 114 ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e); 115 throw exception; 116 } 117 118 setupViewInContext(view, attrs); 119 120 return view; 121 } 122 123 @Override createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext)124 public View createViewFromTag(View parent, String name, AttributeSet attrs, 125 boolean inheritContext) { 126 View view = null; 127 try { 128 view = super.createViewFromTag(parent, name, attrs, inheritContext); 129 } catch (InflateException e) { 130 // try to load the class from using the custom view loader 131 try { 132 view = loadCustomView(name, attrs); 133 } catch (Exception e2) { 134 // Wrap the real exception in an InflateException so that the calling 135 // method can deal with it. 136 InflateException exception = new InflateException(); 137 if (e2.getClass().equals(ClassNotFoundException.class) == false) { 138 exception.initCause(e2); 139 } else { 140 exception.initCause(e); 141 } 142 throw exception; 143 } 144 } 145 146 setupViewInContext(view, attrs); 147 148 return view; 149 } 150 151 @Override inflate(int resource, ViewGroup root)152 public View inflate(int resource, ViewGroup root) { 153 Context context = getContext(); 154 while (context instanceof ContextThemeWrapper) { 155 context = ((ContextThemeWrapper) context).getBaseContext(); 156 } 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 Context context = getContext(); 220 while (context instanceof ContextThemeWrapper) { 221 context = ((ContextThemeWrapper) context).getBaseContext(); 222 } 223 if (context instanceof BridgeContext) { 224 BridgeContext bc = (BridgeContext) context; 225 // get the view key 226 Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge); 227 if (viewKey != null) { 228 bc.addViewKey(view, viewKey); 229 } 230 } 231 } 232 setIsInMerge(boolean isInMerge)233 public void setIsInMerge(boolean isInMerge) { 234 mIsInMerge = isInMerge; 235 } 236 setResourceReference(ResourceReference reference)237 public void setResourceReference(ResourceReference reference) { 238 mResourceReference = reference; 239 } 240 241 @Override cloneInContext(Context newContext)242 public LayoutInflater cloneInContext(Context newContext) { 243 return new BridgeInflater(this, newContext); 244 } 245 getViewKeyFromParser(AttributeSet attrs, BridgeContext bc, ResourceReference resourceReference, boolean isInMerge)246 /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc, 247 ResourceReference resourceReference, boolean isInMerge) { 248 249 if (!(attrs instanceof BridgeXmlBlockParser)) { 250 return null; 251 } 252 BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs); 253 254 // get the view key 255 Object viewKey = parser.getViewCookie(); 256 257 if (viewKey == null) { 258 int currentDepth = parser.getDepth(); 259 260 // test whether we are in an included file or in a adapter binding view. 261 BridgeXmlBlockParser previousParser = bc.getPreviousParser(); 262 if (previousParser != null) { 263 // looks like we are inside an embedded layout. 264 // only apply the cookie of the calling node (<include>) if we are at the 265 // top level of the embedded layout. If there is a merge tag, then 266 // skip it and look for the 2nd level 267 int testDepth = isInMerge ? 2 : 1; 268 if (currentDepth == testDepth) { 269 viewKey = previousParser.getViewCookie(); 270 // if we are in a merge, wrap the cookie in a MergeCookie. 271 if (viewKey != null && isInMerge) { 272 viewKey = new MergeCookie(viewKey); 273 } 274 } 275 } else if (resourceReference != null && currentDepth == 1) { 276 // else if there's a resource reference, this means we are in an adapter 277 // binding case. Set the resource ref as the view cookie only for the top 278 // level view. 279 viewKey = resourceReference; 280 } 281 } 282 283 return viewKey; 284 } 285 } 286