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