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
17package com.android.layoutlib.bridge.intensive.setup;
18
19import com.android.SdkConstants;
20import com.android.ide.common.rendering.api.ActionBarCallback;
21import com.android.ide.common.rendering.api.AdapterBinding;
22import com.android.ide.common.rendering.api.ILayoutPullParser;
23import com.android.ide.common.rendering.api.LayoutlibCallback;
24import com.android.ide.common.rendering.api.ParserFactory;
25import com.android.ide.common.rendering.api.ResourceReference;
26import com.android.ide.common.rendering.api.ResourceValue;
27import com.android.ide.common.rendering.api.SessionParams.Key;
28import com.android.ide.common.resources.IntArrayWrapper;
29import com.android.layoutlib.bridge.android.RenderParamsFlags;
30import com.android.resources.ResourceType;
31import com.android.util.Pair;
32import com.android.utils.ILogger;
33
34import org.kxml2.io.KXmlParser;
35import org.xmlpull.v1.XmlPullParser;
36import org.xmlpull.v1.XmlPullParserException;
37
38import android.annotation.NonNull;
39import android.annotation.Nullable;
40
41import java.io.File;
42import java.io.FileNotFoundException;
43import java.lang.reflect.Constructor;
44import java.lang.reflect.Field;
45import java.lang.reflect.Modifier;
46import java.util.Map;
47
48import com.google.android.collect.Maps;
49
50import static org.junit.Assert.fail;
51
52@SuppressWarnings("deprecation") // For Pair
53public 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
66    public LayoutLibTestCallback(ILogger logger, ClassLoader classLoader) {
67        mLog = logger;
68        mModuleClassLoader = classLoader;
69    }
70
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
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
117    public String getNamespace() {
118        return String.format(SdkConstants.NS_CUSTOM_RESOURCES_S,
119                PACKAGE_NAME);
120    }
121
122    @Override
123    public Pair<ResourceType, String> resolveResourceId(int id) {
124        return mProjectResources.get(id);
125    }
126
127    @Override
128    public String resolveResourceId(int[] id) {
129        return mStyleableValueToNameMap.get(new IntArrayWrapper(id));
130    }
131
132    @Override
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
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
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
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
165    public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
166            Object viewObject) {
167        return null;
168    }
169
170    @Override
171    public ActionBarCallback getActionBarCallback() {
172        return mActionBarCallback;
173    }
174
175    @Override
176    public boolean supports(int ideFeature) {
177        return false;
178    }
179
180    @NonNull
181    @Override
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