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