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