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