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 17package com.android.layoutlib.bridge.android; 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.resources.ResourceType; 26import com.android.util.Pair; 27 28import org.kxml2.io.KXmlParser; 29import org.xmlpull.v1.XmlPullParser; 30 31import android.content.Context; 32import android.util.AttributeSet; 33import android.view.InflateException; 34import android.view.LayoutInflater; 35import android.view.View; 36import android.view.ViewGroup; 37 38import java.io.File; 39import java.io.FileInputStream; 40 41/** 42 * Custom implementation of {@link LayoutInflater} to handle custom views. 43 */ 44public 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 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 */ 71 public BridgeInflater(Context context, IProjectCallback projectCallback) { 72 super(context); 73 mProjectCallback = projectCallback; 74 mConstructorArgs[0] = context; 75 } 76 77 @Override 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 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 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 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} 274