Bridge.java revision 5a09488a158b669577cd8eb557ce4feb62929e75
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
17package com.android.layoutlib.bridge;
18
19import com.android.layoutlib.api.ILayoutLog;
20import com.android.layoutlib.api.IProjectCallback;
21import com.android.layoutlib.api.IResourceValue;
22import com.android.layoutlib.api.IXmlPullParser;
23import com.android.layoutlib.api.LayoutBridge;
24import com.android.layoutlib.api.SceneParams;
25import com.android.layoutlib.api.SceneResult;
26import com.android.layoutlib.bridge.android.BridgeAssetManager;
27import com.android.layoutlib.bridge.impl.FontLoader;
28import com.android.layoutlib.bridge.impl.LayoutSceneImpl;
29import com.android.ninepatch.NinePatch;
30import com.android.tools.layoutlib.create.MethodAdapter;
31import com.android.tools.layoutlib.create.OverrideMethod;
32
33import android.graphics.Bitmap;
34import android.graphics.Typeface_Delegate;
35import android.util.Finalizers;
36
37import java.lang.ref.SoftReference;
38import java.lang.reflect.Field;
39import java.lang.reflect.Modifier;
40import java.util.HashMap;
41import java.util.Map;
42
43/**
44 * Main entry point of the LayoutLib Bridge.
45 * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
46 * {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}.
47 */
48public final class Bridge extends LayoutBridge {
49
50    public static class StaticMethodNotImplementedException extends RuntimeException {
51        private static final long serialVersionUID = 1L;
52
53        public StaticMethodNotImplementedException(String msg) {
54            super(msg);
55        }
56    }
57
58    /**
59     * Maps from id to resource name/type. This is for android.R only.
60     */
61    private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>();
62    /**
63     * Same as sRMap except for int[] instead of int resources. This is for android.R only.
64     */
65    private final static Map<int[], String> sRArrayMap = new HashMap<int[], String>();
66    /**
67     * Reverse map compared to sRMap, resource type -> (resource name -> id).
68     * This is for android.R only.
69     */
70    private final static Map<String, Map<String, Integer>> sRFullMap =
71        new HashMap<String, Map<String,Integer>>();
72
73    private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
74        new HashMap<Object, Map<String, SoftReference<Bitmap>>>();
75    private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache =
76        new HashMap<Object, Map<String, SoftReference<NinePatch>>>();
77
78    private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache =
79        new HashMap<String, SoftReference<Bitmap>>();
80    private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache =
81        new HashMap<String, SoftReference<NinePatch>>();
82
83    private static Map<String, Map<String, Integer>> sEnumValueMap;
84
85    /**
86     * A default logger than prints to stdout/stderr.
87     */
88    private final static ILayoutLog sDefaultLogger = new ILayoutLog() {
89        public void error(String message) {
90            System.err.println(message);
91        }
92
93        public void error(Throwable t) {
94            String message = t.getMessage();
95            if (message == null) {
96                message = t.getClass().getName();
97            }
98
99            System.err.println(message);
100        }
101
102        public void warning(String message) {
103            System.out.println(message);
104        }
105    };
106
107    @Override
108    public int getApiLevel() {
109        return LayoutBridge.API_CURRENT;
110    }
111
112    /*
113     * (non-Javadoc)
114     * @see com.android.layoutlib.api.ILayoutLibBridge#init(java.lang.String, java.util.Map)
115     */
116    @Override
117    public boolean init(String fontOsLocation, Map<String, Map<String, Integer>> enumValueMap) {
118        sEnumValueMap = enumValueMap;
119
120        Finalizers.init();
121
122        BridgeAssetManager.initSystem();
123
124
125        // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
126        // on static (native) methods which prints the signature on the console and
127        // throws an exception.
128        // This is useful when testing the rendering in ADT to identify static native
129        // methods that are ignored -- layoutlib_create makes them returns 0/false/null
130        // which is generally OK yet might be a problem, so this is how you'd find out.
131        //
132        // Currently layoutlib_create only overrides static native method.
133        // Static non-natives are not overridden and thus do not get here.
134        final String debug = System.getenv("DEBUG_LAYOUT");
135        if (debug != null && !debug.equals("0") && !debug.equals("false")) {
136
137            OverrideMethod.setDefaultListener(new MethodAdapter() {
138                @Override
139                public void onInvokeV(String signature, boolean isNative, Object caller) {
140                    sDefaultLogger.error("Missing Stub: " + signature +
141                            (isNative ? " (native)" : ""));
142
143                    if (debug.equalsIgnoreCase("throw")) {
144                        // Throwing this exception doesn't seem that useful. It breaks
145                        // the layout editor yet doesn't display anything meaningful to the
146                        // user. Having the error in the console is just as useful. We'll
147                        // throw it only if the environment variable is "throw" or "THROW".
148                        throw new StaticMethodNotImplementedException(signature);
149                    }
150                }
151            });
152        }
153
154        // load the fonts.
155        FontLoader fontLoader = FontLoader.create(fontOsLocation);
156        if (fontLoader != null) {
157            Typeface_Delegate.init(fontLoader);
158        } else {
159            return false;
160        }
161
162        // now parse com.android.internal.R (and only this one as android.R is a subset of
163        // the internal version), and put the content in the maps.
164        try {
165            // WARNING: this only works because the class is already loaded, and therefore
166            // the objects returned by Field.get() are the same as the ones used by
167            // the code accessing the R class.
168            // int[] does not implement equals/hashCode, and if the parsing used a different class
169            // loader for the R class, this would NOT work.
170            Class<?> r = com.android.internal.R.class;
171
172            for (Class<?> inner : r.getDeclaredClasses()) {
173                String resType = inner.getSimpleName();
174
175                Map<String, Integer> fullMap = new HashMap<String, Integer>();
176                sRFullMap.put(resType, fullMap);
177
178                for (Field f : inner.getDeclaredFields()) {
179                    // only process static final fields. Since the final attribute may have
180                    // been altered by layoutlib_create, we only check static
181                    int modifiers = f.getModifiers();
182                    if (Modifier.isStatic(modifiers)) {
183                        Class<?> type = f.getType();
184                        if (type.isArray() && type.getComponentType() == int.class) {
185                            // if the object is an int[] we put it in sRArrayMap
186                            sRArrayMap.put((int[]) f.get(null), f.getName());
187                        } else if (type == int.class) {
188                            Integer value = (Integer) f.get(null);
189                            sRMap.put(value, new String[] { f.getName(), resType });
190                            fullMap.put(f.getName(), value);
191                        } else {
192                            assert false;
193                        }
194                    }
195                }
196            }
197        } catch (IllegalArgumentException e) {
198            // FIXME: log/return the error (there's no logger object at this point!)
199            e.printStackTrace();
200            return false;
201        } catch (IllegalAccessException e) {
202            e.printStackTrace();
203            return false;
204        }
205
206        return true;
207    }
208
209    @Override
210    public boolean dispose() {
211        BridgeAssetManager.clearSystem();
212        return true;
213    }
214
215    /**
216     * Sets a 9 patch in a project cache or in the framework cache.
217     * @param value the path of the 9 patch
218     * @param ninePatch the 9 patch object
219     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
220     */
221    public static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) {
222        if (projectKey != null) {
223            Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey);
224
225            if (map == null) {
226                map = new HashMap<String, SoftReference<NinePatch>>();
227                sProject9PatchCache.put(projectKey, map);
228            }
229
230            map.put(value, new SoftReference<NinePatch>(ninePatch));
231        } else {
232            sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch));
233        }
234    }
235
236    /**
237     * Starts a layout session by inflating and rendering it. The method returns a
238     * {@link ILayoutScene} on which further actions can be taken.
239     *
240     * @param layoutDescription the {@link IXmlPullParser} letting the LayoutLib Bridge visit the
241     * layout file.
242     * @param projectKey An Object identifying the project. This is used for the cache mechanism.
243     * @param screenWidth the screen width
244     * @param screenHeight the screen height
245     * @param renderFullSize if true, the rendering will render the full size needed by the
246     * layout. This size is never smaller than <var>screenWidth</var> x <var>screenHeight</var>.
247     * @param density the density factor for the screen.
248     * @param xdpi the screen actual dpi in X
249     * @param ydpi the screen actual dpi in Y
250     * @param themeName The name of the theme to use.
251     * @param isProjectTheme true if the theme is a project theme, false if it is a framework theme.
252     * @param projectResources the resources of the project. The map contains (String, map) pairs
253     * where the string is the type of the resource reference used in the layout file, and the
254     * map contains (String, {@link IResourceValue}) pairs where the key is the resource name,
255     * and the value is the resource value.
256     * @param frameworkResources the framework resources. The map contains (String, map) pairs
257     * where the string is the type of the resource reference used in the layout file, and the map
258     * contains (String, {@link IResourceValue}) pairs where the key is the resource name, and the
259     * value is the resource value.
260     * @param projectCallback The {@link IProjectCallback} object to get information from
261     * the project.
262     * @param logger the object responsible for displaying warning/errors to the user.
263     * @return a new {@link ILayoutScene} object that contains the result of the layout.
264     * @since 5
265     */
266    @Override
267    public BridgeLayoutScene createScene(SceneParams params) {
268        try {
269            SceneResult lastResult = SceneResult.SUCCESS;
270            LayoutSceneImpl scene = null;
271            synchronized (this) {
272                try {
273                    scene = new LayoutSceneImpl(params);
274
275                    scene.prepare();
276                    lastResult = scene.inflate();
277                    if (lastResult == SceneResult.SUCCESS) {
278                        lastResult = scene.render();
279                    }
280                } finally {
281                    if (scene != null) {
282                        scene.cleanup();
283                    }
284                }
285            }
286
287            return new BridgeLayoutScene(this, scene, lastResult);
288        } catch (Throwable t) {
289            t.printStackTrace();
290            return new BridgeLayoutScene(this, null, new SceneResult("error!", t));
291        }
292    }
293
294    /*
295     * (non-Javadoc)
296     * @see com.android.layoutlib.api.ILayoutLibBridge#clearCaches(java.lang.Object)
297     */
298    @Override
299    public void clearCaches(Object projectKey) {
300        if (projectKey != null) {
301            sProjectBitmapCache.remove(projectKey);
302            sProject9PatchCache.remove(projectKey);
303        }
304    }
305
306    /**
307     * Returns details of a framework resource from its integer value.
308     * @param value the integer value
309     * @return an array of 2 strings containing the resource name and type, or null if the id
310     * does not match any resource.
311     */
312    public static String[] resolveResourceValue(int value) {
313        return sRMap.get(value);
314
315    }
316
317    /**
318     * Returns the name of a framework resource whose value is an int array.
319     * @param array
320     */
321    public static String resolveResourceValue(int[] array) {
322        return sRArrayMap.get(array);
323    }
324
325    /**
326     * Returns the integer id of a framework resource, from a given resource type and resource name.
327     * @param type the type of the resource
328     * @param name the name of the resource.
329     * @return an {@link Integer} containing the resource id, or null if no resource were found.
330     */
331    public static Integer getResourceValue(String type, String name) {
332        Map<String, Integer> map = sRFullMap.get(type);
333        if (map != null) {
334            return map.get(name);
335        }
336
337        return null;
338    }
339
340    /**
341     * Returns the list of possible enums for a given attribute name.
342     */
343    public static Map<String, Integer> getEnumValues(String attributeName) {
344        if (sEnumValueMap != null) {
345            return sEnumValueMap.get(attributeName);
346        }
347
348        return null;
349    }
350
351    /**
352     * Returns the bitmap for a specific path, from a specific project cache, or from the
353     * framework cache.
354     * @param value the path of the bitmap
355     * @param projectKey the key of the project, or null to query the framework cache.
356     * @return the cached Bitmap or null if not found.
357     */
358    public static Bitmap getCachedBitmap(String value, Object projectKey) {
359        if (projectKey != null) {
360            Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
361            if (map != null) {
362                SoftReference<Bitmap> ref = map.get(value);
363                if (ref != null) {
364                    return ref.get();
365                }
366            }
367        } else {
368            SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
369            if (ref != null) {
370                return ref.get();
371            }
372        }
373
374        return null;
375    }
376
377    /**
378     * Sets a bitmap in a project cache or in the framework cache.
379     * @param value the path of the bitmap
380     * @param bmp the Bitmap object
381     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
382     */
383    public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
384        if (projectKey != null) {
385            Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
386
387            if (map == null) {
388                map = new HashMap<String, SoftReference<Bitmap>>();
389                sProjectBitmapCache.put(projectKey, map);
390            }
391
392            map.put(value, new SoftReference<Bitmap>(bmp));
393        } else {
394            sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
395        }
396    }
397
398    /**
399     * Returns the 9 patch for a specific path, from a specific project cache, or from the
400     * framework cache.
401     * @param value the path of the 9 patch
402     * @param projectKey the key of the project, or null to query the framework cache.
403     * @return the cached 9 patch or null if not found.
404     */
405    public static NinePatch getCached9Patch(String value, Object projectKey) {
406        if (projectKey != null) {
407            Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey);
408
409            if (map != null) {
410                SoftReference<NinePatch> ref = map.get(value);
411                if (ref != null) {
412                    return ref.get();
413                }
414            }
415        } else {
416            SoftReference<NinePatch> ref = sFramework9PatchCache.get(value);
417            if (ref != null) {
418                return ref.get();
419            }
420        }
421
422        return null;
423    }
424}
425