1 /* 2 * Copyright (C) 2014 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 com.android.layoutlib.bridge.intensive.setup; 18 19 import com.android.SdkConstants; 20 import com.android.ide.common.rendering.api.ActionBarCallback; 21 import com.android.ide.common.rendering.api.AdapterBinding; 22 import com.android.ide.common.rendering.api.ILayoutPullParser; 23 import com.android.ide.common.rendering.api.LayoutlibCallback; 24 import com.android.ide.common.rendering.api.ParserFactory; 25 import com.android.ide.common.rendering.api.ResourceReference; 26 import com.android.ide.common.rendering.api.ResourceValue; 27 import com.android.ide.common.rendering.api.SessionParams.Key; 28 import com.android.ide.common.resources.IntArrayWrapper; 29 import com.android.layoutlib.bridge.android.RenderParamsFlags; 30 import com.android.resources.ResourceType; 31 import com.android.util.Pair; 32 import com.android.utils.ILogger; 33 34 import org.kxml2.io.KXmlParser; 35 import org.xmlpull.v1.XmlPullParser; 36 import org.xmlpull.v1.XmlPullParserException; 37 38 import android.annotation.NonNull; 39 import android.annotation.Nullable; 40 41 import java.io.File; 42 import java.io.FileNotFoundException; 43 import java.lang.reflect.Constructor; 44 import java.lang.reflect.Field; 45 import java.lang.reflect.Modifier; 46 import java.util.Map; 47 48 import com.google.android.collect.Maps; 49 50 import static org.junit.Assert.fail; 51 52 @SuppressWarnings("deprecation") // For Pair 53 public class LayoutLibTestCallback extends LayoutlibCallback { 54 55 private static final String PROJECT_CLASSES_LOCATION = "/testApp/MyApplication/build/intermediates/classes/debug/"; 56 private static final String PACKAGE_NAME = "com.android.layoutlib.test.myapplication"; 57 58 private final Map<Integer, Pair<ResourceType, String>> mProjectResources = Maps.newHashMap(); 59 private final Map<IntArrayWrapper, String> mStyleableValueToNameMap = Maps.newHashMap(); 60 private final Map<ResourceType, Map<String, Integer>> mResources = Maps.newHashMap(); 61 private final ILogger mLog; 62 private final ActionBarCallback mActionBarCallback = new ActionBarCallback(); 63 private final ClassLoader mModuleClassLoader; 64 private String mAdaptiveIconMaskPath; 65 LayoutLibTestCallback(ILogger logger, ClassLoader classLoader)66 public LayoutLibTestCallback(ILogger logger, ClassLoader classLoader) { 67 mLog = logger; 68 mModuleClassLoader = classLoader; 69 } 70 initResources()71 public void initResources() throws ClassNotFoundException { 72 Class<?> rClass = mModuleClassLoader.loadClass(PACKAGE_NAME + ".R"); 73 Class<?>[] nestedClasses = rClass.getDeclaredClasses(); 74 for (Class<?> resClass : nestedClasses) { 75 final ResourceType resType = ResourceType.getEnum(resClass.getSimpleName()); 76 77 if (resType != null) { 78 final Map<String, Integer> resName2Id = Maps.newHashMap(); 79 mResources.put(resType, resName2Id); 80 81 for (Field field : resClass.getDeclaredFields()) { 82 final int modifiers = field.getModifiers(); 83 if (Modifier.isStatic(modifiers)) { // May not be final in library projects 84 final Class<?> type = field.getType(); 85 try { 86 if (type.isArray() && type.getComponentType() == int.class) { 87 mStyleableValueToNameMap.put( 88 new IntArrayWrapper((int[]) field.get(null)), 89 field.getName()); 90 } else if (type == int.class) { 91 final Integer value = (Integer) field.get(null); 92 mProjectResources.put(value, Pair.of(resType, field.getName())); 93 resName2Id.put(field.getName(), value); 94 } else { 95 mLog.error(null, "Unknown field type in R class: %1$s", type); 96 } 97 } catch (IllegalAccessException ignored) { 98 mLog.error(ignored, "Malformed R class: %1$s", PACKAGE_NAME + ".R"); 99 } 100 } 101 } 102 } 103 } 104 } 105 106 107 @Override loadView(String name, Class[] constructorSignature, Object[] constructorArgs)108 public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs) 109 throws Exception { 110 Class<?> viewClass = mModuleClassLoader.loadClass(name); 111 Constructor<?> viewConstructor = viewClass.getConstructor(constructorSignature); 112 viewConstructor.setAccessible(true); 113 return viewConstructor.newInstance(constructorArgs); 114 } 115 116 @Override getNamespace()117 public String getNamespace() { 118 return String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, 119 PACKAGE_NAME); 120 } 121 122 @Override resolveResourceId(int id)123 public Pair<ResourceType, String> resolveResourceId(int id) { 124 return mProjectResources.get(id); 125 } 126 127 @Override resolveResourceId(int[] id)128 public String resolveResourceId(int[] id) { 129 return mStyleableValueToNameMap.get(new IntArrayWrapper(id)); 130 } 131 132 @Override getResourceId(ResourceType type, String name)133 public Integer getResourceId(ResourceType type, String name) { 134 Map<String, Integer> resName2Id = mResources.get(type); 135 if (resName2Id == null) { 136 return null; 137 } 138 return resName2Id.get(name); 139 } 140 141 @Override getParser(String layoutName)142 public ILayoutPullParser getParser(String layoutName) { 143 fail("This method shouldn't be called by this version of LayoutLib."); 144 return null; 145 } 146 147 @Override getParser(ResourceValue layoutResource)148 public ILayoutPullParser getParser(ResourceValue layoutResource) { 149 try { 150 return LayoutPullParser.createFromFile(new File(layoutResource.getValue())); 151 } catch (FileNotFoundException e) { 152 return null; 153 } 154 } 155 156 @Override getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, ResourceReference itemRef, int fullPosition, int positionPerType, int fullParentPosition, int parentPositionPerType, ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue)157 public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, 158 ResourceReference itemRef, int fullPosition, int positionPerType, 159 int fullParentPosition, int parentPositionPerType, ResourceReference viewRef, 160 ViewAttribute viewAttribute, Object defaultValue) { 161 return null; 162 } 163 164 @Override getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie, Object viewObject)165 public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie, 166 Object viewObject) { 167 return null; 168 } 169 170 @Override getActionBarCallback()171 public ActionBarCallback getActionBarCallback() { 172 return mActionBarCallback; 173 } 174 175 @Override supports(int ideFeature)176 public boolean supports(int ideFeature) { 177 return false; 178 } 179 180 @NonNull 181 @Override getParserFactory()182 public ParserFactory getParserFactory() { 183 return new ParserFactory() { 184 @NonNull 185 @Override 186 public XmlPullParser createParser(@Nullable String debugName) 187 throws XmlPullParserException { 188 return new KXmlParser(); 189 } 190 }; 191 } 192 193 @Override 194 public <T> T getFlag(Key<T> key) { 195 if (key.equals(RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE)) { 196 return (T) PACKAGE_NAME; 197 } 198 if (key.equals(RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH)) { 199 return (T) mAdaptiveIconMaskPath; 200 } 201 return null; 202 } 203 204 public void setAdaptiveIconMaskPath(String adaptiveIconMaskPath) { 205 mAdaptiveIconMaskPath = adaptiveIconMaskPath; 206 } 207 } 208