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.ResourceReference;
25 import com.android.ide.common.rendering.api.ResourceValue;
26 import com.android.ide.common.rendering.api.SessionParams.Key;
27 import com.android.layoutlib.bridge.android.RenderParamsFlags;
28 import com.android.resources.ResourceType;
29 import com.android.utils.ILogger;
30 
31 import org.kxml2.io.KXmlParser;
32 import org.xmlpull.v1.XmlPullParser;
33 import org.xmlpull.v1.XmlPullParserException;
34 
35 import android.annotation.NonNull;
36 import android.annotation.Nullable;
37 
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.lang.reflect.Constructor;
45 import java.lang.reflect.Field;
46 import java.lang.reflect.Modifier;
47 import java.util.HashMap;
48 import java.util.Map;
49 
50 import com.google.common.io.ByteStreams;
51 
52 import static com.android.ide.common.rendering.api.ResourceNamespace.RES_AUTO;
53 
54 public class LayoutLibTestCallback extends LayoutlibCallback {
55     private static final String PACKAGE_NAME = "com.android.layoutlib.test.myapplication";
56 
57     private final Map<Integer, ResourceReference> mProjectResources = new HashMap<>();
58     private final Map<ResourceReference, Integer> mResources = new HashMap<>();
59     private final ILogger mLog;
60     private final ActionBarCallback mActionBarCallback = new ActionBarCallback();
61     private final ClassLoader mModuleClassLoader;
62     private String mAdaptiveIconMaskPath;
63     private boolean mSetUseShadow = true;
64     private boolean mHighShadowQuality = true;
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.fromClassName(resClass.getSimpleName());
76 
77             if (resType != null) {
78                 for (Field field : resClass.getDeclaredFields()) {
79                     final int modifiers = field.getModifiers();
80                     if (Modifier.isStatic(modifiers)) { // May not be final in library projects
81                         final Class<?> type = field.getType();
82                         try {
83                             if (type == int.class) {
84                                 final Integer value = (Integer) field.get(null);
85                                 ResourceReference reference =
86                                         new ResourceReference(RES_AUTO, resType, field.getName());
87                                 mProjectResources.put(value, reference);
88                                 mResources.put(reference, value);
89                             } else if (!(type.isArray() && type.getComponentType() == int.class)) {
90                                 mLog.error(null, "Unknown field type in R class: %1$s", type);
91                             }
92                         } catch (IllegalAccessException e) {
93                             mLog.error(e, "Malformed R class: %1$s", PACKAGE_NAME + ".R");
94                         }
95                     }
96                 }
97             }
98         }
99     }
100 
101 
102     @Override
loadView(@onNull String name, @NonNull Class[] constructorSignature, Object[] constructorArgs)103     public Object loadView(@NonNull String name, @NonNull Class[] constructorSignature, Object[] constructorArgs)
104             throws Exception {
105         Class<?> viewClass = mModuleClassLoader.loadClass(name);
106         Constructor<?> viewConstructor = viewClass.getConstructor(constructorSignature);
107         viewConstructor.setAccessible(true);
108         return viewConstructor.newInstance(constructorArgs);
109     }
110 
111     @Override
resolveResourceId(int id)112     public ResourceReference resolveResourceId(int id) {
113         return mProjectResources.get(id);
114     }
115 
116     @Override
getOrGenerateResourceId(@onNull ResourceReference resource)117     public int getOrGenerateResourceId(@NonNull ResourceReference resource) {
118         Integer id = mResources.get(resource);
119         return id != null ? id : 0;
120     }
121 
122     @Override
getParser(@onNull ResourceValue layoutResource)123     public ILayoutPullParser getParser(@NonNull ResourceValue layoutResource) {
124         try {
125             return LayoutPullParser.createFromFile(new File(layoutResource.getValue()));
126         } catch (FileNotFoundException e) {
127             return null;
128         }
129     }
130 
131     @Override
getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, ResourceReference itemRef, int fullPosition, int positionPerType, int fullParentPosition, int parentPositionPerType, ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue)132     public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
133             ResourceReference itemRef, int fullPosition, int positionPerType,
134             int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
135             ViewAttribute viewAttribute, Object defaultValue) {
136         return null;
137     }
138 
139     @Override
getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie, Object viewObject)140     public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
141             Object viewObject) {
142         return null;
143     }
144 
145     @Override
getActionBarCallback()146     public ActionBarCallback getActionBarCallback() {
147         return mActionBarCallback;
148     }
149 
150     @Override
151     @Nullable
createXmlParserForPsiFile(@onNull String fileName)152     public XmlPullParser createXmlParserForPsiFile(@NonNull String fileName) {
153         return createXmlParserForFile(fileName);
154     }
155 
156     @Override
157     @Nullable
createXmlParserForFile(@onNull String fileName)158     public XmlPullParser createXmlParserForFile(@NonNull String fileName) {
159         try (FileInputStream fileStream = new FileInputStream(fileName)) {
160             // Read data fully to memory to be able to close the file stream.
161             ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
162             ByteStreams.copy(fileStream, byteOutputStream);
163             KXmlParser parser = new KXmlParser();
164             parser.setInput(new ByteArrayInputStream(byteOutputStream.toByteArray()), null);
165             parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
166             return parser;
167         } catch (IOException | XmlPullParserException e) {
168             return null;
169         }
170     }
171 
172     @Override
173     @NonNull
createXmlParser()174     public XmlPullParser createXmlParser() {
175         return new KXmlParser();
176     }
177 
178     @Override
179     @SuppressWarnings("unchecked") // The Key<T> API is based on unchecked casts.
getFlag(Key<T> key)180     public <T> T getFlag(Key<T> key) {
181         if (key.equals(RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE)) {
182             return (T) PACKAGE_NAME;
183         }
184         if (key.equals(RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH)) {
185             return (T) mAdaptiveIconMaskPath;
186         }
187         return null;
188     }
189 
190     @Override
findClass(String name)191     public Class<?> findClass(String name) throws ClassNotFoundException {
192         return mModuleClassLoader.loadClass(name);
193     }
194 
setAdaptiveIconMaskPath(String adaptiveIconMaskPath)195     public void setAdaptiveIconMaskPath(String adaptiveIconMaskPath) {
196         mAdaptiveIconMaskPath = adaptiveIconMaskPath;
197     }
198 }
199