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.LayoutLog; 20 import com.android.ide.common.rendering.api.LayoutlibCallback; 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.BridgeConstants; 26 import com.android.layoutlib.bridge.android.BridgeContext; 27 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 28 import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil; 29 import com.android.layoutlib.bridge.android.support.RecyclerViewUtil; 30 import com.android.layoutlib.bridge.impl.ParserFactory; 31 import com.android.layoutlib.bridge.util.ReflectionUtils; 32 import com.android.resources.ResourceType; 33 import com.android.util.Pair; 34 35 import org.xmlpull.v1.XmlPullParser; 36 37 import android.annotation.NonNull; 38 import android.content.Context; 39 import android.util.AttributeSet; 40 41 import java.io.File; 42 import java.util.HashMap; 43 import java.util.Map; 44 45 import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext; 46 47 /** 48 * Custom implementation of {@link LayoutInflater} to handle custom views. 49 */ 50 public final class BridgeInflater extends LayoutInflater { 51 52 private final LayoutlibCallback mLayoutlibCallback; 53 private boolean mIsInMerge = false; 54 private ResourceReference mResourceReference; 55 private Map<View, String> mOpenDrawerLayouts; 56 57 /** 58 * List of class prefixes which are tried first by default. 59 * <p/> 60 * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater. 61 */ 62 private static final String[] sClassPrefixList = { 63 "android.widget.", 64 "android.webkit.", 65 "android.app." 66 }; 67 getClassPrefixList()68 public static String[] getClassPrefixList() { 69 return sClassPrefixList; 70 } 71 BridgeInflater(LayoutInflater original, Context newContext)72 protected BridgeInflater(LayoutInflater original, Context newContext) { 73 super(original, newContext); 74 newContext = getBaseContext(newContext); 75 if (newContext instanceof BridgeContext) { 76 mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback(); 77 } else { 78 mLayoutlibCallback = null; 79 } 80 } 81 82 /** 83 * Instantiate a new BridgeInflater with an {@link LayoutlibCallback} object. 84 * 85 * @param context The Android application context. 86 * @param layoutlibCallback the {@link LayoutlibCallback} object. 87 */ BridgeInflater(Context context, LayoutlibCallback layoutlibCallback)88 public BridgeInflater(Context context, LayoutlibCallback layoutlibCallback) { 89 super(context); 90 mLayoutlibCallback = layoutlibCallback; 91 mConstructorArgs[0] = context; 92 } 93 94 @Override onCreateView(String name, AttributeSet attrs)95 public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { 96 View view = null; 97 98 try { 99 // First try to find a class using the default Android prefixes 100 for (String prefix : sClassPrefixList) { 101 try { 102 view = createView(name, prefix, attrs); 103 if (view != null) { 104 break; 105 } 106 } catch (ClassNotFoundException e) { 107 // Ignore. We'll try again using the base class below. 108 } 109 } 110 111 // Next try using the parent loader. This will most likely only work for 112 // fully-qualified class names. 113 try { 114 if (view == null) { 115 view = super.onCreateView(name, attrs); 116 } 117 } catch (ClassNotFoundException e) { 118 // Ignore. We'll try again using the custom view loader below. 119 } 120 121 // Finally try again using the custom view loader 122 if (view == null) { 123 view = loadCustomView(name, attrs); 124 } 125 } catch (Exception e) { 126 // Wrap the real exception in a ClassNotFoundException, so that the calling method 127 // can deal with it. 128 throw new ClassNotFoundException("onCreateView", e); 129 } 130 131 setupViewInContext(view, attrs); 132 133 return view; 134 } 135 136 @Override createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttrs)137 public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, 138 boolean ignoreThemeAttrs) { 139 View view; 140 try { 141 view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttrs); 142 } catch (InflateException e) { 143 // try to load the class from using the custom view loader 144 try { 145 view = loadCustomView(name, attrs); 146 } catch (Exception e2) { 147 // Wrap the real exception in an InflateException so that the calling 148 // method can deal with it. 149 InflateException exception = new InflateException(); 150 if (!e2.getClass().equals(ClassNotFoundException.class)) { 151 exception.initCause(e2); 152 } else { 153 exception.initCause(e); 154 } 155 throw exception; 156 } 157 } 158 159 setupViewInContext(view, attrs); 160 161 return view; 162 } 163 164 @Override inflate(int resource, ViewGroup root)165 public View inflate(int resource, ViewGroup root) { 166 Context context = getContext(); 167 context = getBaseContext(context); 168 if (context instanceof BridgeContext) { 169 BridgeContext bridgeContext = (BridgeContext)context; 170 171 ResourceValue value = null; 172 173 @SuppressWarnings("deprecation") 174 Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource); 175 if (layoutInfo != null) { 176 value = bridgeContext.getRenderResources().getFrameworkResource( 177 ResourceType.LAYOUT, layoutInfo.getSecond()); 178 } else { 179 layoutInfo = mLayoutlibCallback.resolveResourceId(resource); 180 181 if (layoutInfo != null) { 182 value = bridgeContext.getRenderResources().getProjectResource( 183 ResourceType.LAYOUT, layoutInfo.getSecond()); 184 } 185 } 186 187 if (value != null) { 188 File f = new File(value.getValue()); 189 if (f.isFile()) { 190 try { 191 XmlPullParser parser = ParserFactory.create(f); 192 193 BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( 194 parser, bridgeContext, value.isFramework()); 195 196 return inflate(bridgeParser, root); 197 } catch (Exception e) { 198 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, 199 "Failed to parse file " + f.getAbsolutePath(), e, null); 200 201 return null; 202 } 203 } 204 } 205 } 206 return null; 207 } 208 loadCustomView(String name, AttributeSet attrs)209 private View loadCustomView(String name, AttributeSet attrs) throws Exception { 210 if (mLayoutlibCallback != null) { 211 // first get the classname in case it's not the node name 212 if (name.equals("view")) { 213 name = attrs.getAttributeValue(null, "class"); 214 } 215 216 mConstructorArgs[1] = attrs; 217 218 Object customView = mLayoutlibCallback.loadView(name, mConstructorSignature, 219 mConstructorArgs); 220 221 if (customView instanceof View) { 222 return (View)customView; 223 } 224 } 225 226 return null; 227 } 228 setupViewInContext(View view, AttributeSet attrs)229 private void setupViewInContext(View view, AttributeSet attrs) { 230 Context context = getContext(); 231 context = getBaseContext(context); 232 if (context instanceof BridgeContext) { 233 BridgeContext bc = (BridgeContext) context; 234 // get the view key 235 Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge); 236 if (viewKey != null) { 237 bc.addViewKey(view, viewKey); 238 } 239 String scrollPos = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY"); 240 if (scrollPos != null) { 241 if (scrollPos.endsWith("px")) { 242 int value = Integer.parseInt(scrollPos.substring(0, scrollPos.length() - 2)); 243 bc.setScrollYPos(view, value); 244 } 245 } 246 if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) { 247 Integer resourceId = null; 248 String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, 249 BridgeConstants.ATTR_LIST_ITEM); 250 if (attrVal != null && !attrVal.isEmpty()) { 251 ResourceValue resValue = bc.getRenderResources().findResValue(attrVal, false); 252 if (resValue.isFramework()) { 253 resourceId = Bridge.getResourceId(resValue.getResourceType(), 254 resValue.getName()); 255 } else { 256 resourceId = mLayoutlibCallback.getResourceId(resValue.getResourceType(), 257 resValue.getName()); 258 } 259 } 260 if (resourceId == null) { 261 resourceId = 0; 262 } 263 RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId); 264 } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) { 265 String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, 266 BridgeConstants.ATTR_OPEN_DRAWER); 267 if (attrVal != null) { 268 getDrawerLayoutMap().put(view, attrVal); 269 } 270 } 271 272 } 273 } 274 setIsInMerge(boolean isInMerge)275 public void setIsInMerge(boolean isInMerge) { 276 mIsInMerge = isInMerge; 277 } 278 setResourceReference(ResourceReference reference)279 public void setResourceReference(ResourceReference reference) { 280 mResourceReference = reference; 281 } 282 283 @Override cloneInContext(Context newContext)284 public LayoutInflater cloneInContext(Context newContext) { 285 return new BridgeInflater(this, newContext); 286 } 287 getViewKeyFromParser(AttributeSet attrs, BridgeContext bc, ResourceReference resourceReference, boolean isInMerge)288 /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc, 289 ResourceReference resourceReference, boolean isInMerge) { 290 291 if (!(attrs instanceof BridgeXmlBlockParser)) { 292 return null; 293 } 294 BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs); 295 296 // get the view key 297 Object viewKey = parser.getViewCookie(); 298 299 if (viewKey == null) { 300 int currentDepth = parser.getDepth(); 301 302 // test whether we are in an included file or in a adapter binding view. 303 BridgeXmlBlockParser previousParser = bc.getPreviousParser(); 304 if (previousParser != null) { 305 // looks like we are inside an embedded layout. 306 // only apply the cookie of the calling node (<include>) if we are at the 307 // top level of the embedded layout. If there is a merge tag, then 308 // skip it and look for the 2nd level 309 int testDepth = isInMerge ? 2 : 1; 310 if (currentDepth == testDepth) { 311 viewKey = previousParser.getViewCookie(); 312 // if we are in a merge, wrap the cookie in a MergeCookie. 313 if (viewKey != null && isInMerge) { 314 viewKey = new MergeCookie(viewKey); 315 } 316 } 317 } else if (resourceReference != null && currentDepth == 1) { 318 // else if there's a resource reference, this means we are in an adapter 319 // binding case. Set the resource ref as the view cookie only for the top 320 // level view. 321 viewKey = resourceReference; 322 } 323 } 324 325 return viewKey; 326 } 327 postInflateProcess(View view)328 public void postInflateProcess(View view) { 329 if (mOpenDrawerLayouts != null) { 330 String gravity = mOpenDrawerLayouts.get(view); 331 if (gravity != null) { 332 DrawerLayoutUtil.openDrawer(view, gravity); 333 } 334 mOpenDrawerLayouts.remove(view); 335 } 336 } 337 338 @NonNull getDrawerLayoutMap()339 private Map<View, String> getDrawerLayoutMap() { 340 if (mOpenDrawerLayouts == null) { 341 mOpenDrawerLayouts = new HashMap<View, String>(4); 342 } 343 return mOpenDrawerLayouts; 344 } 345 onDoneInflation()346 public void onDoneInflation() { 347 if (mOpenDrawerLayouts != null) { 348 mOpenDrawerLayouts.clear(); 349 } 350 } 351 } 352