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